htmlnano 3.1.0 → 3.2.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.
- package/README.md +37 -23
- package/dist/_modules/mergeScripts.js +29 -17
- package/dist/_modules/mergeScripts.mjs +29 -17
- package/dist/_modules/mergeStyles.js +25 -15
- package/dist/_modules/mergeStyles.mjs +25 -15
- package/dist/_modules/minifyJs.js +12 -0
- package/dist/_modules/minifyJs.mjs +12 -0
- package/dist/_modules/removeUnusedCss.js +3 -2
- package/dist/_modules/removeUnusedCss.mjs +3 -2
- package/dist/_modules/sortAttributes.d.mts +3 -2
- package/dist/_modules/sortAttributes.d.ts +3 -2
- package/dist/_modules/sortAttributes.js +4 -3
- package/dist/_modules/sortAttributes.mjs +4 -3
- package/dist/_modules/sortAttributesWithLists.d.mts +3 -1
- package/dist/_modules/sortAttributesWithLists.d.ts +3 -1
- package/dist/_modules/sortAttributesWithLists.js +43 -56
- package/dist/_modules/sortAttributesWithLists.mjs +43 -56
- package/dist/presets/max.js +9 -5
- package/dist/presets/max.mjs +9 -5
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -5,28 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
Modular HTML minifier, built on top of the [PostHTML](https://github.com/posthtml/posthtml). Inspired by [cssnano](https://github.com/cssnano/cssnano).
|
|
7
7
|
|
|
8
|
-
## Benchmarks
|
|
9
|
-
|
|
10
|
-
[html-minifier-terser]: https://www.npmjs.com/package/html-minifier-terser/v/7.2.0
|
|
11
|
-
[html-minifier-next]: https://www.npmjs.com/package/html-minifier-next/v/4.15.2
|
|
12
|
-
[htmlnano]: https://www.npmjs.com/package/htmlnano/v/3.1.0
|
|
13
|
-
[minify]: https://www.npmjs.com/package/@tdewolff/minify/v/2.24.8
|
|
14
|
-
[minify-html]: https://www.npmjs.com/package/@minify-html/node/v/0.18.1
|
|
15
|
-
|
|
16
|
-
| Website | Source (KB) | [html-minifier-terser] | [html-minifier-next] | [htmlnano] | [minify] | [minify-html] |
|
|
17
|
-
| ------------------------------------------------------------- | ----------: | ---------------------: | -------------------: | ---------: | --------: | ------------: |
|
|
18
|
-
| [stackoverflow.blog](https://stackoverflow.blog/) | 142 | 3.7% | 32.3% | 6.8% | 4.5% | 4.6% |
|
|
19
|
-
| [github.com](https://github.com/) | 549 | 2.9% | 42.2% | 16.6% | 7.3% | 5.7% |
|
|
20
|
-
| [en.wikipedia.org](https://en.wikipedia.org/wiki/Main_Page) | 218 | 4.6% | 7.7% | 7.4% | 6.2% | 2.9% |
|
|
21
|
-
| [developer.mozilla.org](https://developer.mozilla.org/en-US/) | 109 | 37.9% | 42.0% | 52.7% | 40.1% | 39.9% |
|
|
22
|
-
| [tc39.es](https://tc39.es/ecma262/) | 7243 | 8.5% | 11.8% | 9.3% | 9.5% | 9.1% |
|
|
23
|
-
| [apple.com](https://www.apple.com/) | 210 | 9.2% | 14.4% | 11.2% | 10.3% | 9.8% |
|
|
24
|
-
| [w3.org](https://www.w3.org/) | 50 | 19.0% | 24.6% | 23.4% | 24.4% | 20.3% |
|
|
25
|
-
| [weather.com](https://weather.com) | 1960 | 0.4% | 11.2% | 19.8% | 11.6% | 0.6% |
|
|
26
|
-
| **Avg. minify rate** | | **10.8%** | **23.3%** | **18.4%** | **14.2%** | **11.6%** |
|
|
27
|
-
|
|
28
|
-
Latest benchmarks: https://github.com/maltsev/html-minifiers-benchmark (updated daily).
|
|
29
|
-
|
|
30
8
|
## Documentation
|
|
31
9
|
https://htmlnano.netlify.app
|
|
32
10
|
|
|
@@ -68,4 +46,40 @@ Also, you can use it as CLI tool:
|
|
|
68
46
|
node_modules/.bin/htmlnano --help
|
|
69
47
|
```
|
|
70
48
|
|
|
71
|
-
More usage examples (PostHTML,
|
|
49
|
+
More usage examples (PostHTML, CLI, Webpack): https://htmlnano.netlify.app/usage
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
## Benchmarks
|
|
53
|
+
|
|
54
|
+
[html-minifier-terser]: https://www.npmjs.com/package/html-minifier-terser/v/7.2.0
|
|
55
|
+
[html-minifier-next]: https://www.npmjs.com/package/html-minifier-next/v/5.0.3
|
|
56
|
+
[htmlnano]: https://www.npmjs.com/package/htmlnano/v/3.1.0
|
|
57
|
+
[minify]: https://www.npmjs.com/package/@tdewolff/minify/v/2.24.8
|
|
58
|
+
[minify-html]: https://www.npmjs.com/package/@minify-html/node/v/0.18.1
|
|
59
|
+
|
|
60
|
+
| Website | Source (KB) | [html-minifier-terser] | [html-minifier-next] | [htmlnano] | [minify] | [minify-html] |
|
|
61
|
+
| --------------------------------------------------------------- | ----------: | ---------------------: | -------------------: | ---------: | --------: | ------------: |
|
|
62
|
+
| [alistapart.com](https://alistapart.com/) | 63 | 7.6% | 11.6% | 34.6% | 11.1% | 8.6% |
|
|
63
|
+
| [developer.mozilla.org](https://developer.mozilla.org/en-US/) | 109 | 38.0% | 41.7% | 52.8% | 40.1% | 39.9% |
|
|
64
|
+
| [css-tricks.com](https://css-tricks.com) | 11 | 8.2% | 34.1% | 37.2% | 18.8% | 8.5% |
|
|
65
|
+
| [en.wikipedia.org](https://en.wikipedia.org/wiki/Main_Page) | 224 | 4.5% | 7.4% | 7.2% | 60.6% | 2.9% |
|
|
66
|
+
| [github.com](https://github.com/) | 546 | 3.0% | 9.7% | 16.7% | 7.3% | 5.7% |
|
|
67
|
+
| [edri.org](https://edri.org) | 80 | 7.7% | 12.3% | 30.6% | 12.3% | 8.2% |
|
|
68
|
+
| [leanpub.com](https://leanpub.com) | 251 | 1.3% | 6.9% | 6.3% | 6.0% | 1.7% |
|
|
69
|
+
| [stackoverflow.blog](https://stackoverflow.blog/) | 139 | 3.9% | 5.7% | 7.0% | 4.6% | 4.7% |
|
|
70
|
+
| [html.spec.whatwg.org](https://html.spec.whatwg.org/multipage/) | 149 | -3.9% | 0.7% | -2.6% | 0.3% | 0.2% |
|
|
71
|
+
| [eff.org](https://eff.org) | 54 | 8.8% | 14.7% | 10.9% | 13.4% | 9.7% |
|
|
72
|
+
| [apple.com](https://apple.com/) | 229 | 8.9% | 12.5% | 11.5% | 10.4% | 9.5% |
|
|
73
|
+
| [w3.org](https://w3.org/) | 50 | 19.0% | 24.6% | 23.4% | 24.4% | 20.3% |
|
|
74
|
+
| [mastodon.social](https://mastodon.social/explore) | 37 | 3.4% | 6.8% | 14.6% | 5.9% | 3.6% |
|
|
75
|
+
| [bbc.co.uk](https://bbc.co.uk) | 694 | 0.8% | 6.3% | 5.9% | 4.7% | 1.2% |
|
|
76
|
+
| [un.org](https://un.org/en/) | 151 | 14.2% | 22.5% | 41.2% | 20.0% | 15.0% |
|
|
77
|
+
| [lafrenchtech.gouv.fr](https://lafrenchtech.gouv.fr/) | 152 | 13.2% | 17.9% | 64.1% | 17.0% | 13.8% |
|
|
78
|
+
| [sitepoint.com](https://sitepoint.com) | 497 | 0.8% | 7.4% | 12.9% | 6.1% | 0.9% |
|
|
79
|
+
| [faz.net](https://faz.net/aktuell/) | 1572 | 3.4% | 8.0% | 15.8% | 4.8% | 3.6% |
|
|
80
|
+
| [weather.com](https://weather.com) | 2395 | 0.3% | 11.4% | 18.1% | 11.0% | 0.6% |
|
|
81
|
+
| [tc39.es](https://tc39.es/ecma262/) | 7254 | 8.5% | 11.3% | 9.3% | 9.5% | 9.1% |
|
|
82
|
+
| [home.cern](https://home.cern) | 151 | 37.1% | 46.4% | 40.2% | 38.9% | 39.5% |
|
|
83
|
+
| **Avg. minify rate** | | **9.0%** | **15.2%** | **21.8%** | **15.6%** | **9.9%** |
|
|
84
|
+
|
|
85
|
+
Latest benchmarks: https://github.com/maltsev/html-minifiers-benchmark (updated daily).
|
|
@@ -2,6 +2,23 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
2
2
|
|
|
3
3
|
var helpers_js = require('../helpers.js');
|
|
4
4
|
|
|
5
|
+
function normalizeAttrsForKey(attrs, config) {
|
|
6
|
+
const normalized = {
|
|
7
|
+
...config.baseAttrs
|
|
8
|
+
};
|
|
9
|
+
for (const [key, value] of Object.entries(attrs || {})){
|
|
10
|
+
if (config.skippedAttrs.has(key) || value === undefined) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
if (config.booleanAttrs.has(key)) {
|
|
14
|
+
normalized[key] = true;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
normalized[key] = value;
|
|
18
|
+
}
|
|
19
|
+
return normalized;
|
|
20
|
+
}
|
|
21
|
+
|
|
5
22
|
function normalizeAsyncAttr(attrs) {
|
|
6
23
|
if (!attrs) {
|
|
7
24
|
return;
|
|
@@ -25,24 +42,19 @@ const booleanAttrs = new Set([
|
|
|
25
42
|
'defer',
|
|
26
43
|
'nomodule'
|
|
27
44
|
]);
|
|
45
|
+
const skippedAttrs = new Set([
|
|
46
|
+
'src',
|
|
47
|
+
'integrity',
|
|
48
|
+
'type'
|
|
49
|
+
]);
|
|
28
50
|
function normalizeScriptAttrsForKey(attrs, scriptType) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (value === undefined) {
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (booleanAttrs.has(key)) {
|
|
40
|
-
normalized[key] = true;
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
normalized[key] = value;
|
|
44
|
-
}
|
|
45
|
-
return normalized;
|
|
51
|
+
return normalizeAttrsForKey(attrs, {
|
|
52
|
+
baseAttrs: {
|
|
53
|
+
type: scriptType
|
|
54
|
+
},
|
|
55
|
+
booleanAttrs,
|
|
56
|
+
skippedAttrs
|
|
57
|
+
});
|
|
46
58
|
}
|
|
47
59
|
function buildScriptKey(attrs, scriptType, scriptSrcIndex) {
|
|
48
60
|
const normalizedAttrs = normalizeScriptAttrsForKey(attrs, scriptType);
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import { extractTextContentFromNode } from '../helpers.mjs';
|
|
2
2
|
|
|
3
|
+
function normalizeAttrsForKey(attrs, config) {
|
|
4
|
+
const normalized = {
|
|
5
|
+
...config.baseAttrs
|
|
6
|
+
};
|
|
7
|
+
for (const [key, value] of Object.entries(attrs || {})){
|
|
8
|
+
if (config.skippedAttrs.has(key) || value === undefined) {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
if (config.booleanAttrs.has(key)) {
|
|
12
|
+
normalized[key] = true;
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
normalized[key] = value;
|
|
16
|
+
}
|
|
17
|
+
return normalized;
|
|
18
|
+
}
|
|
19
|
+
|
|
3
20
|
function normalizeAsyncAttr(attrs) {
|
|
4
21
|
if (!attrs) {
|
|
5
22
|
return;
|
|
@@ -23,24 +40,19 @@ const booleanAttrs = new Set([
|
|
|
23
40
|
'defer',
|
|
24
41
|
'nomodule'
|
|
25
42
|
]);
|
|
43
|
+
const skippedAttrs = new Set([
|
|
44
|
+
'src',
|
|
45
|
+
'integrity',
|
|
46
|
+
'type'
|
|
47
|
+
]);
|
|
26
48
|
function normalizeScriptAttrsForKey(attrs, scriptType) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (value === undefined) {
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
if (booleanAttrs.has(key)) {
|
|
38
|
-
normalized[key] = true;
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
normalized[key] = value;
|
|
42
|
-
}
|
|
43
|
-
return normalized;
|
|
49
|
+
return normalizeAttrsForKey(attrs, {
|
|
50
|
+
baseAttrs: {
|
|
51
|
+
type: scriptType
|
|
52
|
+
},
|
|
53
|
+
booleanAttrs,
|
|
54
|
+
skippedAttrs
|
|
55
|
+
});
|
|
44
56
|
}
|
|
45
57
|
function buildScriptKey(attrs, scriptType, scriptSrcIndex) {
|
|
46
58
|
const normalizedAttrs = normalizeScriptAttrsForKey(attrs, scriptType);
|
|
@@ -2,10 +2,31 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
2
2
|
|
|
3
3
|
var helpers_js = require('../helpers.js');
|
|
4
4
|
|
|
5
|
+
function normalizeAttrsForKey(attrs, config) {
|
|
6
|
+
const normalized = {
|
|
7
|
+
...config.baseAttrs
|
|
8
|
+
};
|
|
9
|
+
for (const [key, value] of Object.entries(attrs || {})){
|
|
10
|
+
if (config.skippedAttrs.has(key) || value === undefined) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
if (config.booleanAttrs.has(key)) {
|
|
14
|
+
normalized[key] = true;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
normalized[key] = value;
|
|
18
|
+
}
|
|
19
|
+
return normalized;
|
|
20
|
+
}
|
|
21
|
+
|
|
5
22
|
const booleanAttrs = new Set([
|
|
6
23
|
'amp-custom',
|
|
7
24
|
'disabled'
|
|
8
25
|
]);
|
|
26
|
+
const skippedAttrs = new Set([
|
|
27
|
+
'type',
|
|
28
|
+
'media'
|
|
29
|
+
]);
|
|
9
30
|
function normalizeStyleType(attrs) {
|
|
10
31
|
if (!attrs || typeof attrs.type !== 'string') {
|
|
11
32
|
return 'text/css';
|
|
@@ -21,21 +42,10 @@ function normalizeStyleMedia(attrs) {
|
|
|
21
42
|
return media ? media.replace(/\s+/g, ' ').toLowerCase() : 'all';
|
|
22
43
|
}
|
|
23
44
|
function normalizeStyleAttrsForKey(attrs) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
if (value === undefined) {
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
if (booleanAttrs.has(key)) {
|
|
33
|
-
normalized[key] = true;
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
normalized[key] = value;
|
|
37
|
-
}
|
|
38
|
-
return normalized;
|
|
45
|
+
return normalizeAttrsForKey(attrs, {
|
|
46
|
+
booleanAttrs,
|
|
47
|
+
skippedAttrs
|
|
48
|
+
});
|
|
39
49
|
}
|
|
40
50
|
function buildStyleKey(attrs) {
|
|
41
51
|
const keyObject = {
|
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
import { isAmpBoilerplate, extractTextContentFromNode } from '../helpers.mjs';
|
|
2
2
|
|
|
3
|
+
function normalizeAttrsForKey(attrs, config) {
|
|
4
|
+
const normalized = {
|
|
5
|
+
...config.baseAttrs
|
|
6
|
+
};
|
|
7
|
+
for (const [key, value] of Object.entries(attrs || {})){
|
|
8
|
+
if (config.skippedAttrs.has(key) || value === undefined) {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
if (config.booleanAttrs.has(key)) {
|
|
12
|
+
normalized[key] = true;
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
normalized[key] = value;
|
|
16
|
+
}
|
|
17
|
+
return normalized;
|
|
18
|
+
}
|
|
19
|
+
|
|
3
20
|
const booleanAttrs = new Set([
|
|
4
21
|
'amp-custom',
|
|
5
22
|
'disabled'
|
|
6
23
|
]);
|
|
24
|
+
const skippedAttrs = new Set([
|
|
25
|
+
'type',
|
|
26
|
+
'media'
|
|
27
|
+
]);
|
|
7
28
|
function normalizeStyleType(attrs) {
|
|
8
29
|
if (!attrs || typeof attrs.type !== 'string') {
|
|
9
30
|
return 'text/css';
|
|
@@ -19,21 +40,10 @@ function normalizeStyleMedia(attrs) {
|
|
|
19
40
|
return media ? media.replace(/\s+/g, ' ').toLowerCase() : 'all';
|
|
20
41
|
}
|
|
21
42
|
function normalizeStyleAttrsForKey(attrs) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
if (value === undefined) {
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
if (booleanAttrs.has(key)) {
|
|
31
|
-
normalized[key] = true;
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
normalized[key] = value;
|
|
35
|
-
}
|
|
36
|
-
return normalized;
|
|
43
|
+
return normalizeAttrsForKey(attrs, {
|
|
44
|
+
booleanAttrs,
|
|
45
|
+
skippedAttrs
|
|
46
|
+
});
|
|
37
47
|
}
|
|
38
48
|
function buildStyleKey(attrs) {
|
|
39
49
|
const keyObject = {
|
|
@@ -192,10 +192,22 @@ function processNodeWithOnAttrs(node, terserOptions, terser) {
|
|
|
192
192
|
const minifiedJs = code.substring(jsWrapperStart.length, code.length - jsWrapperEnd.length);
|
|
193
193
|
node.attrs[attrName] = minifiedJs;
|
|
194
194
|
}
|
|
195
|
+
}).catch((error)=>{
|
|
196
|
+
// Skip invalid inline handler code and preserve the original value.
|
|
197
|
+
if (isTerserParseError(error)) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
throw error;
|
|
195
201
|
});
|
|
196
202
|
promises.push(promise);
|
|
197
203
|
}
|
|
198
204
|
return promises;
|
|
199
205
|
}
|
|
206
|
+
function isTerserParseError(error) {
|
|
207
|
+
if (!(error instanceof Error)) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
return error.name === 'SyntaxError' || error.message.includes('JS_Parse_Error');
|
|
211
|
+
}
|
|
200
212
|
|
|
201
213
|
exports.default = mod;
|
|
@@ -190,10 +190,22 @@ function processNodeWithOnAttrs(node, terserOptions, terser) {
|
|
|
190
190
|
const minifiedJs = code.substring(jsWrapperStart.length, code.length - jsWrapperEnd.length);
|
|
191
191
|
node.attrs[attrName] = minifiedJs;
|
|
192
192
|
}
|
|
193
|
+
}).catch((error)=>{
|
|
194
|
+
// Skip invalid inline handler code and preserve the original value.
|
|
195
|
+
if (isTerserParseError(error)) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
throw error;
|
|
193
199
|
});
|
|
194
200
|
promises.push(promise);
|
|
195
201
|
}
|
|
196
202
|
return promises;
|
|
197
203
|
}
|
|
204
|
+
function isTerserParseError(error) {
|
|
205
|
+
if (!(error instanceof Error)) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
return error.name === 'SyntaxError' || error.message.includes('JS_Parse_Error');
|
|
209
|
+
}
|
|
198
210
|
|
|
199
211
|
export { mod as default };
|
|
@@ -112,13 +112,14 @@ function runPurgecss(tree, css, userOptions, purgecss, extractor) {
|
|
|
112
112
|
}
|
|
113
113
|
/** Remove unused CSS */ const mod = {
|
|
114
114
|
async default (tree, options, userOptions) {
|
|
115
|
+
var _resolvedOptions_tool;
|
|
115
116
|
const promises = [];
|
|
116
117
|
let html;
|
|
117
118
|
let extractor;
|
|
118
119
|
const purgecss = await helpers_js.optionalImport('purgecss');
|
|
119
120
|
const uncss = await helpers_js.optionalImport('uncss');
|
|
120
121
|
const resolvedOptions = resolveUserOptions(userOptions);
|
|
121
|
-
const tool = resolvedOptions.tool;
|
|
122
|
+
const tool = (_resolvedOptions_tool = resolvedOptions.tool) != null ? _resolvedOptions_tool : 'purgeCSS';
|
|
122
123
|
const toolOptions = stripToolOption(resolvedOptions);
|
|
123
124
|
tree.walk((node)=>{
|
|
124
125
|
if (helpers_js.isStyleNode(node) && helpers_js.isCssStyleType(node)) {
|
|
@@ -127,7 +128,7 @@ function runPurgecss(tree, css, userOptions, purgecss, extractor) {
|
|
|
127
128
|
extractor != null ? extractor : extractor = purgeFromHtml(tree);
|
|
128
129
|
promises.push(processStyleNodePurgeCSS(tree, node, toolOptions, purgecss, extractor));
|
|
129
130
|
}
|
|
130
|
-
} else {
|
|
131
|
+
} else if (tool === 'uncss') {
|
|
131
132
|
if (uncss) {
|
|
132
133
|
html != null ? html : html = tree.render(tree);
|
|
133
134
|
promises.push(processStyleNodeUnCSS(html, node, toolOptions, uncss));
|
|
@@ -110,13 +110,14 @@ function runPurgecss(tree, css, userOptions, purgecss, extractor) {
|
|
|
110
110
|
}
|
|
111
111
|
/** Remove unused CSS */ const mod = {
|
|
112
112
|
async default (tree, options, userOptions) {
|
|
113
|
+
var _resolvedOptions_tool;
|
|
113
114
|
const promises = [];
|
|
114
115
|
let html;
|
|
115
116
|
let extractor;
|
|
116
117
|
const purgecss = await optionalImport('purgecss');
|
|
117
118
|
const uncss = await optionalImport('uncss');
|
|
118
119
|
const resolvedOptions = resolveUserOptions(userOptions);
|
|
119
|
-
const tool = resolvedOptions.tool;
|
|
120
|
+
const tool = (_resolvedOptions_tool = resolvedOptions.tool) != null ? _resolvedOptions_tool : 'purgeCSS';
|
|
120
121
|
const toolOptions = stripToolOption(resolvedOptions);
|
|
121
122
|
tree.walk((node)=>{
|
|
122
123
|
if (isStyleNode(node) && isCssStyleType(node)) {
|
|
@@ -125,7 +126,7 @@ function runPurgecss(tree, css, userOptions, purgecss, extractor) {
|
|
|
125
126
|
extractor != null ? extractor : extractor = purgeFromHtml(tree);
|
|
126
127
|
promises.push(processStyleNodePurgeCSS(tree, node, toolOptions, purgecss, extractor));
|
|
127
128
|
}
|
|
128
|
-
} else {
|
|
129
|
+
} else if (tool === 'uncss') {
|
|
129
130
|
if (uncss) {
|
|
130
131
|
html != null ? html : html = tree.render(tree);
|
|
131
132
|
promises.push(processStyleNodeUnCSS(html, node, toolOptions, uncss));
|
|
@@ -85,7 +85,8 @@ type HtmlnanoModule<Options = any> = {
|
|
|
85
85
|
default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
|
|
86
86
|
};
|
|
87
87
|
|
|
88
|
-
type
|
|
89
|
-
|
|
88
|
+
type SortAttributesOption = 'alphabetical' | 'frequency';
|
|
89
|
+
|
|
90
|
+
declare const mod: HtmlnanoModule<boolean | SortAttributesOption>;
|
|
90
91
|
|
|
91
92
|
export { mod as default };
|
|
@@ -85,7 +85,8 @@ type HtmlnanoModule<Options = any> = {
|
|
|
85
85
|
default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
|
|
86
86
|
};
|
|
87
87
|
|
|
88
|
-
type
|
|
89
|
-
|
|
88
|
+
type SortAttributesOption = 'alphabetical' | 'frequency';
|
|
89
|
+
|
|
90
|
+
declare const mod: HtmlnanoModule<boolean | SortAttributesOption>;
|
|
90
91
|
|
|
91
92
|
export { mod as default };
|
|
@@ -4,11 +4,12 @@ const validOptions = new Set([
|
|
|
4
4
|
'frequency',
|
|
5
5
|
'alphabetical'
|
|
6
6
|
]);
|
|
7
|
-
|
|
7
|
+
function resolveSortType(options) {
|
|
8
8
|
if (options === true) return 'alphabetical';
|
|
9
9
|
if (options === false) return false;
|
|
10
10
|
return validOptions.has(options) ? options : false;
|
|
11
|
-
}
|
|
11
|
+
}
|
|
12
|
+
|
|
12
13
|
class AttributeTokenChain {
|
|
13
14
|
addFromNodeAttrs(nodeAttrs) {
|
|
14
15
|
Object.keys(nodeAttrs).forEach((attrName)=>{
|
|
@@ -70,7 +71,7 @@ class AttributeTokenChain {
|
|
|
70
71
|
}
|
|
71
72
|
/** Sort attibutes */ const mod = {
|
|
72
73
|
default (tree, options, moduleOptions) {
|
|
73
|
-
const sortType =
|
|
74
|
+
const sortType = resolveSortType(moduleOptions);
|
|
74
75
|
if (sortType === 'alphabetical') {
|
|
75
76
|
return sortAttributesInAlphabeticalOrder(tree);
|
|
76
77
|
}
|
|
@@ -2,11 +2,12 @@ const validOptions = new Set([
|
|
|
2
2
|
'frequency',
|
|
3
3
|
'alphabetical'
|
|
4
4
|
]);
|
|
5
|
-
|
|
5
|
+
function resolveSortType(options) {
|
|
6
6
|
if (options === true) return 'alphabetical';
|
|
7
7
|
if (options === false) return false;
|
|
8
8
|
return validOptions.has(options) ? options : false;
|
|
9
|
-
}
|
|
9
|
+
}
|
|
10
|
+
|
|
10
11
|
class AttributeTokenChain {
|
|
11
12
|
addFromNodeAttrs(nodeAttrs) {
|
|
12
13
|
Object.keys(nodeAttrs).forEach((attrName)=>{
|
|
@@ -68,7 +69,7 @@ class AttributeTokenChain {
|
|
|
68
69
|
}
|
|
69
70
|
/** Sort attibutes */ const mod = {
|
|
70
71
|
default (tree, options, moduleOptions) {
|
|
71
|
-
const sortType =
|
|
72
|
+
const sortType = resolveSortType(moduleOptions);
|
|
72
73
|
if (sortType === 'alphabetical') {
|
|
73
74
|
return sortAttributesInAlphabeticalOrder(tree);
|
|
74
75
|
}
|
|
@@ -85,6 +85,8 @@ type HtmlnanoModule<Options = any> = {
|
|
|
85
85
|
default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
|
|
86
86
|
};
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
type SortAttributesOption = 'alphabetical' | 'frequency';
|
|
89
|
+
|
|
90
|
+
declare const mod: HtmlnanoModule<boolean | SortAttributesOption>;
|
|
89
91
|
|
|
90
92
|
export { mod as default };
|
|
@@ -85,6 +85,8 @@ type HtmlnanoModule<Options = any> = {
|
|
|
85
85
|
default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
|
|
86
86
|
};
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
type SortAttributesOption = 'alphabetical' | 'frequency';
|
|
89
|
+
|
|
90
|
+
declare const mod: HtmlnanoModule<boolean | SortAttributesOption>;
|
|
89
91
|
|
|
90
92
|
export { mod as default };
|
|
@@ -2,35 +2,36 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
2
2
|
|
|
3
3
|
var collapseAttributeWhitespace_js = require('./collapseAttributeWhitespace.js');
|
|
4
4
|
|
|
5
|
-
// class, rel, ping
|
|
6
5
|
const validOptions = new Set([
|
|
7
6
|
'frequency',
|
|
8
7
|
'alphabetical'
|
|
9
8
|
]);
|
|
10
|
-
|
|
9
|
+
function resolveSortType(options) {
|
|
11
10
|
if (options === true) return 'alphabetical';
|
|
12
11
|
if (options === false) return false;
|
|
13
12
|
return validOptions.has(options) ? options : false;
|
|
14
|
-
}
|
|
15
|
-
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// class, rel, ping
|
|
16
|
+
class ListAttributeTokenChain {
|
|
16
17
|
addFromNodeAttrsArray(attrValuesArray) {
|
|
17
18
|
attrValuesArray.forEach((attrValue)=>{
|
|
18
19
|
if (!attrValue) {
|
|
19
20
|
return;
|
|
20
21
|
}
|
|
21
|
-
if (this.
|
|
22
|
-
this.
|
|
22
|
+
if (this.tokenCounts.has(attrValue)) {
|
|
23
|
+
this.tokenCounts.set(attrValue, this.tokenCounts.get(attrValue) + 1);
|
|
23
24
|
} else {
|
|
24
|
-
this.
|
|
25
|
+
this.tokenCounts.set(attrValue, 1);
|
|
25
26
|
}
|
|
26
27
|
});
|
|
27
28
|
}
|
|
28
29
|
createSortOrder() {
|
|
29
|
-
const
|
|
30
|
-
...this.
|
|
30
|
+
const nextSortOrder = [
|
|
31
|
+
...this.tokenCounts.entries()
|
|
31
32
|
];
|
|
32
|
-
|
|
33
|
-
this.
|
|
33
|
+
nextSortOrder.sort((a, b)=>b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
34
|
+
this.sortedTokens = nextSortOrder.map((i)=>i[0]);
|
|
34
35
|
}
|
|
35
36
|
sortFromNodeAttrsArray(attrValuesArray) {
|
|
36
37
|
const resultArray = [];
|
|
@@ -42,10 +43,10 @@ class AttributeTokenChain {
|
|
|
42
43
|
}
|
|
43
44
|
tokenCounts.set(attrValue, ((_tokenCounts_get = tokenCounts.get(attrValue)) != null ? _tokenCounts_get : 0) + 1);
|
|
44
45
|
});
|
|
45
|
-
if (!this.
|
|
46
|
+
if (!this.sortedTokens) {
|
|
46
47
|
this.createSortOrder();
|
|
47
48
|
}
|
|
48
|
-
this.
|
|
49
|
+
this.sortedTokens.forEach((k)=>{
|
|
49
50
|
const count = tokenCounts.get(k);
|
|
50
51
|
if (!count) {
|
|
51
52
|
return;
|
|
@@ -57,13 +58,13 @@ class AttributeTokenChain {
|
|
|
57
58
|
return resultArray;
|
|
58
59
|
}
|
|
59
60
|
constructor(){
|
|
60
|
-
/** <attr, frequency> */ this.
|
|
61
|
-
this.
|
|
61
|
+
/** <attr, frequency> */ this.tokenCounts = new Map();
|
|
62
|
+
this.sortedTokens = null;
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
65
|
/** Sort values inside list-like attributes (e.g. class, rel) */ const mod = {
|
|
65
66
|
default (tree, options, moduleOptions) {
|
|
66
|
-
const sortType =
|
|
67
|
+
const sortType = resolveSortType(moduleOptions);
|
|
67
68
|
if (sortType === 'alphabetical') {
|
|
68
69
|
return sortAttributesWithListsInAlphabeticalOrder(tree);
|
|
69
70
|
}
|
|
@@ -75,64 +76,50 @@ class AttributeTokenChain {
|
|
|
75
76
|
}
|
|
76
77
|
};
|
|
77
78
|
const splitListAttributeValues = (attrValue)=>attrValue.split(/\s+/).filter(Boolean);
|
|
78
|
-
function
|
|
79
|
+
function walkListAttributes(tree, walkFn) {
|
|
79
80
|
tree.walk((node)=>{
|
|
80
81
|
if (!node.attrs) {
|
|
81
82
|
return node;
|
|
82
83
|
}
|
|
83
84
|
const tagName = node.tag ? node.tag.toLowerCase() : undefined;
|
|
84
|
-
Object.
|
|
85
|
+
Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
|
|
85
86
|
const attrNameLower = attrName.toLowerCase();
|
|
86
|
-
if (!collapseAttributeWhitespace_js.isListAttribute(attrNameLower, tagName)) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
const attrValues = splitListAttributeValues(node.attrs[attrName]);
|
|
90
|
-
if (attrValues.length < 2) {
|
|
87
|
+
if (!collapseAttributeWhitespace_js.isListAttribute(attrNameLower, tagName) || typeof attrValues !== 'string') {
|
|
91
88
|
return;
|
|
92
89
|
}
|
|
93
|
-
node.attrs
|
|
94
|
-
// @ts-expect-error -- deliberately use minus operator to sort things
|
|
95
|
-
return typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b;
|
|
96
|
-
}).join(' ');
|
|
90
|
+
walkFn(node.attrs, attrName, attrValues);
|
|
97
91
|
});
|
|
98
92
|
return node;
|
|
99
93
|
});
|
|
94
|
+
}
|
|
95
|
+
function sortAttributesWithListsInAlphabeticalOrder(tree) {
|
|
96
|
+
walkListAttributes(tree, (nodeAttrs, attrName, attrValues)=>{
|
|
97
|
+
const values = splitListAttributeValues(attrValues);
|
|
98
|
+
if (values.length < 2) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
nodeAttrs[attrName] = values.sort((a, b)=>{
|
|
102
|
+
// @ts-expect-error -- deliberately use minus operator to sort things
|
|
103
|
+
return typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b;
|
|
104
|
+
}).join(' ');
|
|
105
|
+
});
|
|
100
106
|
return tree;
|
|
101
107
|
}
|
|
102
108
|
function sortAttributesWithListsByFrequency(tree) {
|
|
103
|
-
const tokenChainObj = {};
|
|
109
|
+
const tokenChainObj = {};
|
|
104
110
|
// Traverse through tree to get frequency
|
|
105
|
-
tree
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const tagName = node.tag ? node.tag.toLowerCase() : undefined;
|
|
110
|
-
Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
|
|
111
|
-
const attrNameLower = attrName.toLowerCase();
|
|
112
|
-
if (!collapseAttributeWhitespace_js.isListAttribute(attrNameLower, tagName)) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
tokenChainObj[attrNameLower] = tokenChainObj[attrNameLower] || new AttributeTokenChain();
|
|
116
|
-
tokenChainObj[attrNameLower].addFromNodeAttrsArray(splitListAttributeValues(attrValues));
|
|
117
|
-
});
|
|
118
|
-
return node;
|
|
111
|
+
walkListAttributes(tree, (_nodeAttrs, attrName, attrValues)=>{
|
|
112
|
+
const attrNameLower = attrName.toLowerCase();
|
|
113
|
+
tokenChainObj[attrNameLower] = tokenChainObj[attrNameLower] || new ListAttributeTokenChain();
|
|
114
|
+
tokenChainObj[attrNameLower].addFromNodeAttrsArray(splitListAttributeValues(attrValues));
|
|
119
115
|
});
|
|
120
116
|
// Traverse through tree again, this time sort the attribute values
|
|
121
|
-
tree
|
|
122
|
-
|
|
123
|
-
|
|
117
|
+
walkListAttributes(tree, (nodeAttrs, attrName, attrValues)=>{
|
|
118
|
+
const attrNameLower = attrName.toLowerCase();
|
|
119
|
+
if (!tokenChainObj[attrNameLower]) {
|
|
120
|
+
return;
|
|
124
121
|
}
|
|
125
|
-
|
|
126
|
-
Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
|
|
127
|
-
const attrNameLower = attrName.toLowerCase();
|
|
128
|
-
if (!collapseAttributeWhitespace_js.isListAttribute(attrNameLower, tagName)) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
if (tokenChainObj[attrNameLower]) {
|
|
132
|
-
node.attrs[attrName] = tokenChainObj[attrNameLower].sortFromNodeAttrsArray(splitListAttributeValues(attrValues)).join(' ');
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
return node;
|
|
122
|
+
nodeAttrs[attrName] = tokenChainObj[attrNameLower].sortFromNodeAttrsArray(splitListAttributeValues(attrValues)).join(' ');
|
|
136
123
|
});
|
|
137
124
|
return tree;
|
|
138
125
|
}
|
|
@@ -1,34 +1,35 @@
|
|
|
1
1
|
import { isListAttribute } from './collapseAttributeWhitespace.mjs';
|
|
2
2
|
|
|
3
|
-
// class, rel, ping
|
|
4
3
|
const validOptions = new Set([
|
|
5
4
|
'frequency',
|
|
6
5
|
'alphabetical'
|
|
7
6
|
]);
|
|
8
|
-
|
|
7
|
+
function resolveSortType(options) {
|
|
9
8
|
if (options === true) return 'alphabetical';
|
|
10
9
|
if (options === false) return false;
|
|
11
10
|
return validOptions.has(options) ? options : false;
|
|
12
|
-
}
|
|
13
|
-
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// class, rel, ping
|
|
14
|
+
class ListAttributeTokenChain {
|
|
14
15
|
addFromNodeAttrsArray(attrValuesArray) {
|
|
15
16
|
attrValuesArray.forEach((attrValue)=>{
|
|
16
17
|
if (!attrValue) {
|
|
17
18
|
return;
|
|
18
19
|
}
|
|
19
|
-
if (this.
|
|
20
|
-
this.
|
|
20
|
+
if (this.tokenCounts.has(attrValue)) {
|
|
21
|
+
this.tokenCounts.set(attrValue, this.tokenCounts.get(attrValue) + 1);
|
|
21
22
|
} else {
|
|
22
|
-
this.
|
|
23
|
+
this.tokenCounts.set(attrValue, 1);
|
|
23
24
|
}
|
|
24
25
|
});
|
|
25
26
|
}
|
|
26
27
|
createSortOrder() {
|
|
27
|
-
const
|
|
28
|
-
...this.
|
|
28
|
+
const nextSortOrder = [
|
|
29
|
+
...this.tokenCounts.entries()
|
|
29
30
|
];
|
|
30
|
-
|
|
31
|
-
this.
|
|
31
|
+
nextSortOrder.sort((a, b)=>b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
32
|
+
this.sortedTokens = nextSortOrder.map((i)=>i[0]);
|
|
32
33
|
}
|
|
33
34
|
sortFromNodeAttrsArray(attrValuesArray) {
|
|
34
35
|
const resultArray = [];
|
|
@@ -40,10 +41,10 @@ class AttributeTokenChain {
|
|
|
40
41
|
}
|
|
41
42
|
tokenCounts.set(attrValue, ((_tokenCounts_get = tokenCounts.get(attrValue)) != null ? _tokenCounts_get : 0) + 1);
|
|
42
43
|
});
|
|
43
|
-
if (!this.
|
|
44
|
+
if (!this.sortedTokens) {
|
|
44
45
|
this.createSortOrder();
|
|
45
46
|
}
|
|
46
|
-
this.
|
|
47
|
+
this.sortedTokens.forEach((k)=>{
|
|
47
48
|
const count = tokenCounts.get(k);
|
|
48
49
|
if (!count) {
|
|
49
50
|
return;
|
|
@@ -55,13 +56,13 @@ class AttributeTokenChain {
|
|
|
55
56
|
return resultArray;
|
|
56
57
|
}
|
|
57
58
|
constructor(){
|
|
58
|
-
/** <attr, frequency> */ this.
|
|
59
|
-
this.
|
|
59
|
+
/** <attr, frequency> */ this.tokenCounts = new Map();
|
|
60
|
+
this.sortedTokens = null;
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
/** Sort values inside list-like attributes (e.g. class, rel) */ const mod = {
|
|
63
64
|
default (tree, options, moduleOptions) {
|
|
64
|
-
const sortType =
|
|
65
|
+
const sortType = resolveSortType(moduleOptions);
|
|
65
66
|
if (sortType === 'alphabetical') {
|
|
66
67
|
return sortAttributesWithListsInAlphabeticalOrder(tree);
|
|
67
68
|
}
|
|
@@ -73,64 +74,50 @@ class AttributeTokenChain {
|
|
|
73
74
|
}
|
|
74
75
|
};
|
|
75
76
|
const splitListAttributeValues = (attrValue)=>attrValue.split(/\s+/).filter(Boolean);
|
|
76
|
-
function
|
|
77
|
+
function walkListAttributes(tree, walkFn) {
|
|
77
78
|
tree.walk((node)=>{
|
|
78
79
|
if (!node.attrs) {
|
|
79
80
|
return node;
|
|
80
81
|
}
|
|
81
82
|
const tagName = node.tag ? node.tag.toLowerCase() : undefined;
|
|
82
|
-
Object.
|
|
83
|
+
Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
|
|
83
84
|
const attrNameLower = attrName.toLowerCase();
|
|
84
|
-
if (!isListAttribute(attrNameLower, tagName)) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
const attrValues = splitListAttributeValues(node.attrs[attrName]);
|
|
88
|
-
if (attrValues.length < 2) {
|
|
85
|
+
if (!isListAttribute(attrNameLower, tagName) || typeof attrValues !== 'string') {
|
|
89
86
|
return;
|
|
90
87
|
}
|
|
91
|
-
node.attrs
|
|
92
|
-
// @ts-expect-error -- deliberately use minus operator to sort things
|
|
93
|
-
return typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b;
|
|
94
|
-
}).join(' ');
|
|
88
|
+
walkFn(node.attrs, attrName, attrValues);
|
|
95
89
|
});
|
|
96
90
|
return node;
|
|
97
91
|
});
|
|
92
|
+
}
|
|
93
|
+
function sortAttributesWithListsInAlphabeticalOrder(tree) {
|
|
94
|
+
walkListAttributes(tree, (nodeAttrs, attrName, attrValues)=>{
|
|
95
|
+
const values = splitListAttributeValues(attrValues);
|
|
96
|
+
if (values.length < 2) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
nodeAttrs[attrName] = values.sort((a, b)=>{
|
|
100
|
+
// @ts-expect-error -- deliberately use minus operator to sort things
|
|
101
|
+
return typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b;
|
|
102
|
+
}).join(' ');
|
|
103
|
+
});
|
|
98
104
|
return tree;
|
|
99
105
|
}
|
|
100
106
|
function sortAttributesWithListsByFrequency(tree) {
|
|
101
|
-
const tokenChainObj = {};
|
|
107
|
+
const tokenChainObj = {};
|
|
102
108
|
// Traverse through tree to get frequency
|
|
103
|
-
tree
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const tagName = node.tag ? node.tag.toLowerCase() : undefined;
|
|
108
|
-
Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
|
|
109
|
-
const attrNameLower = attrName.toLowerCase();
|
|
110
|
-
if (!isListAttribute(attrNameLower, tagName)) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
tokenChainObj[attrNameLower] = tokenChainObj[attrNameLower] || new AttributeTokenChain();
|
|
114
|
-
tokenChainObj[attrNameLower].addFromNodeAttrsArray(splitListAttributeValues(attrValues));
|
|
115
|
-
});
|
|
116
|
-
return node;
|
|
109
|
+
walkListAttributes(tree, (_nodeAttrs, attrName, attrValues)=>{
|
|
110
|
+
const attrNameLower = attrName.toLowerCase();
|
|
111
|
+
tokenChainObj[attrNameLower] = tokenChainObj[attrNameLower] || new ListAttributeTokenChain();
|
|
112
|
+
tokenChainObj[attrNameLower].addFromNodeAttrsArray(splitListAttributeValues(attrValues));
|
|
117
113
|
});
|
|
118
114
|
// Traverse through tree again, this time sort the attribute values
|
|
119
|
-
tree
|
|
120
|
-
|
|
121
|
-
|
|
115
|
+
walkListAttributes(tree, (nodeAttrs, attrName, attrValues)=>{
|
|
116
|
+
const attrNameLower = attrName.toLowerCase();
|
|
117
|
+
if (!tokenChainObj[attrNameLower]) {
|
|
118
|
+
return;
|
|
122
119
|
}
|
|
123
|
-
|
|
124
|
-
Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
|
|
125
|
-
const attrNameLower = attrName.toLowerCase();
|
|
126
|
-
if (!isListAttribute(attrNameLower, tagName)) {
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
if (tokenChainObj[attrNameLower]) {
|
|
130
|
-
node.attrs[attrName] = tokenChainObj[attrNameLower].sortFromNodeAttrsArray(splitListAttributeValues(attrValues)).join(' ');
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
return node;
|
|
120
|
+
nodeAttrs[attrName] = tokenChainObj[attrNameLower].sortFromNodeAttrsArray(splitListAttributeValues(attrValues)).join(' ');
|
|
134
121
|
});
|
|
135
122
|
return tree;
|
|
136
123
|
}
|
package/dist/presets/max.js
CHANGED
|
@@ -10,23 +10,27 @@ var safePreset__default = /*#__PURE__*/_interopDefault(safePreset);
|
|
|
10
10
|
* Maximal minification (might break some pages)
|
|
11
11
|
*/ var max = {
|
|
12
12
|
...safePreset__default.default,
|
|
13
|
+
removeRedundantAttributes: true,
|
|
14
|
+
sortAttributes: true,
|
|
13
15
|
collapseWhitespace: 'all',
|
|
14
16
|
removeComments: 'all',
|
|
17
|
+
removeEmptyElements: true,
|
|
18
|
+
minifyConditionalComments: true,
|
|
19
|
+
removeOptionalTags: true,
|
|
15
20
|
removeAttributeQuotes: true,
|
|
16
|
-
removeRedundantAttributes: true,
|
|
17
21
|
minifyAttributes: {
|
|
18
22
|
metaContent: true,
|
|
19
23
|
redundantWhitespaces: 'agressive'
|
|
20
24
|
},
|
|
21
25
|
mergeScripts: true,
|
|
22
26
|
mergeStyles: true,
|
|
23
|
-
removeUnusedCss: {
|
|
27
|
+
removeUnusedCss: {
|
|
28
|
+
tool: 'purgeCSS'
|
|
29
|
+
},
|
|
24
30
|
minifyCss: {
|
|
25
31
|
preset: 'default'
|
|
26
32
|
},
|
|
27
|
-
minifySvg: {}
|
|
28
|
-
minifyConditionalComments: true,
|
|
29
|
-
removeOptionalTags: true
|
|
33
|
+
minifySvg: {}
|
|
30
34
|
};
|
|
31
35
|
|
|
32
36
|
exports.default = max;
|
package/dist/presets/max.mjs
CHANGED
|
@@ -4,23 +4,27 @@ import safePreset from './safe.mjs';
|
|
|
4
4
|
* Maximal minification (might break some pages)
|
|
5
5
|
*/ var max = {
|
|
6
6
|
...safePreset,
|
|
7
|
+
removeRedundantAttributes: true,
|
|
8
|
+
sortAttributes: true,
|
|
7
9
|
collapseWhitespace: 'all',
|
|
8
10
|
removeComments: 'all',
|
|
11
|
+
removeEmptyElements: true,
|
|
12
|
+
minifyConditionalComments: true,
|
|
13
|
+
removeOptionalTags: true,
|
|
9
14
|
removeAttributeQuotes: true,
|
|
10
|
-
removeRedundantAttributes: true,
|
|
11
15
|
minifyAttributes: {
|
|
12
16
|
metaContent: true,
|
|
13
17
|
redundantWhitespaces: 'agressive'
|
|
14
18
|
},
|
|
15
19
|
mergeScripts: true,
|
|
16
20
|
mergeStyles: true,
|
|
17
|
-
removeUnusedCss: {
|
|
21
|
+
removeUnusedCss: {
|
|
22
|
+
tool: 'purgeCSS'
|
|
23
|
+
},
|
|
18
24
|
minifyCss: {
|
|
19
25
|
preset: 'default'
|
|
20
26
|
},
|
|
21
|
-
minifySvg: {}
|
|
22
|
-
minifyConditionalComments: true,
|
|
23
|
-
removeOptionalTags: true
|
|
27
|
+
minifySvg: {}
|
|
24
28
|
};
|
|
25
29
|
|
|
26
30
|
export { max as default };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "htmlnano",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Modular HTML minifier, built on top of the PostHTML",
|
|
5
5
|
"author": "Kirill Maltsev <maltsevkirill@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
"build": "npm run clean && bunchee",
|
|
10
10
|
"postbuild": "chmod +x dist/bin.js",
|
|
11
11
|
"compile": "npm run build",
|
|
12
|
-
"lint": "eslint --fix .",
|
|
12
|
+
"lint:eslint": "eslint --fix .",
|
|
13
|
+
"lint:duplicates": "jscpd",
|
|
14
|
+
"lint:knip": "knip",
|
|
15
|
+
"lint": "npm run lint:eslint && npm run lint:duplicates && npm run lint:knip",
|
|
13
16
|
"test:mocha": "mocha --timeout 5000 --require @swc-node/register --recursive --check-leaks --globals addresses 'test/**/*.ts'",
|
|
14
17
|
"pretest": "npm run lint && npm run compile",
|
|
15
18
|
"test": "c8 -r text -r html npm run test:mocha",
|
|
@@ -69,15 +72,16 @@
|
|
|
69
72
|
"@types/mocha": "^10.0.10",
|
|
70
73
|
"@types/node": "^25.0.0",
|
|
71
74
|
"bunchee": "^6.5.1",
|
|
72
|
-
"c8": "^
|
|
75
|
+
"c8": "^11.0.0",
|
|
73
76
|
"cssnano": "^7.0.0",
|
|
74
77
|
"eslint": "^9.25.1",
|
|
75
78
|
"eslint-config-flat-gitignore": "^2.1.0",
|
|
76
|
-
"eslint-plugin-import": "^2.28.1",
|
|
77
79
|
"eslint-plugin-import-x": "^4.11.0",
|
|
78
80
|
"eslint-plugin-unused-imports": "^4.1.4",
|
|
79
81
|
"expect": "^30.1.1",
|
|
80
82
|
"globals": "^17.0.0",
|
|
83
|
+
"jscpd": "^4.0.8",
|
|
84
|
+
"knip": "^5.83.1",
|
|
81
85
|
"mocha": "^11.0.1",
|
|
82
86
|
"postcss": "^8.3.11",
|
|
83
87
|
"purgecss": "^8.0.0",
|