htmlnano 0.2.9 → 1.1.1
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/CHANGELOG.md +57 -2
- package/README.md +15 -886
- package/docs/README.md +33 -0
- package/docs/babel.config.js +3 -0
- package/docs/docs/010-introduction.md +22 -0
- package/docs/docs/020-usage.md +77 -0
- package/docs/docs/030-config.md +21 -0
- package/docs/docs/040-presets.md +75 -0
- package/docs/docs/050-modules.md +786 -0
- package/docs/docs/060-contribute.md +16 -0
- package/docs/docusaurus.config.js +60 -0
- package/docs/netlify.toml +4 -0
- package/docs/package-lock.json +11621 -0
- package/docs/package.json +39 -0
- package/docs/sidebars.js +26 -0
- package/docs/versioned_docs/version-1.1.1/010-introduction.md +22 -0
- package/docs/versioned_docs/version-1.1.1/020-usage.md +77 -0
- package/docs/versioned_docs/version-1.1.1/030-config.md +21 -0
- package/docs/versioned_docs/version-1.1.1/040-presets.md +75 -0
- package/docs/versioned_docs/version-1.1.1/050-modules.md +786 -0
- package/docs/versioned_docs/version-1.1.1/060-contribute.md +16 -0
- package/docs/versioned_sidebars/version-1.1.1-sidebars.json +8 -0
- package/docs/versions.json +3 -0
- package/lib/helpers.js +5 -0
- package/lib/htmlnano.js +43 -6
- package/lib/modules/collapseAttributeWhitespace.js +62 -6
- package/lib/modules/collapseWhitespace.js +42 -17
- package/lib/modules/minifyCss.js +4 -2
- package/lib/modules/minifyJs.js +5 -3
- package/lib/modules/minifySvg.js +6 -12
- package/lib/modules/minifyUrls.js +50 -15
- package/lib/modules/normalizeAttributeValues.js +48 -0
- package/lib/modules/removeComments.js +25 -1
- package/lib/modules/removeEmptyAttributes.js +52 -8
- package/lib/modules/removeRedundantAttributes.js +69 -14
- package/lib/presets/safe.js +9 -4
- package/package.json +18 -16
- package/test.js +25 -16
- package/uncss-fork.patch +13 -0
|
@@ -4,8 +4,54 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = removeEmptyAttributes;
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const safeToRemoveAttrs = {
|
|
8
|
+
id: null,
|
|
9
|
+
class: null,
|
|
10
|
+
style: null,
|
|
11
|
+
title: null,
|
|
12
|
+
lang: null,
|
|
13
|
+
dir: null,
|
|
14
|
+
abbr: ['th'],
|
|
15
|
+
accept: ['input'],
|
|
16
|
+
'accept-charset': ['form'],
|
|
17
|
+
charset: ['meta', 'script'],
|
|
18
|
+
action: ['form'],
|
|
19
|
+
cols: ['textarea'],
|
|
20
|
+
colspan: ['td', 'th'],
|
|
21
|
+
coords: ['area'],
|
|
22
|
+
dirname: ['input', 'textarea'],
|
|
23
|
+
dropzone: null,
|
|
24
|
+
headers: ['td', 'th'],
|
|
25
|
+
form: ['button', 'fieldset', 'input', 'keygen', 'object', 'output', 'select', 'textarea'],
|
|
26
|
+
formaction: ['button', 'input'],
|
|
27
|
+
height: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video'],
|
|
28
|
+
high: 'meter',
|
|
29
|
+
href: 'link',
|
|
30
|
+
list: 'input',
|
|
31
|
+
low: 'meter',
|
|
32
|
+
manifest: 'html',
|
|
33
|
+
max: ['meter', 'progress'],
|
|
34
|
+
maxLength: ['input', 'textarea'],
|
|
35
|
+
menu: 'button',
|
|
36
|
+
min: 'meter',
|
|
37
|
+
minLength: ['input', 'textarea'],
|
|
38
|
+
name: ['button', 'fieldset', 'input', 'keygen', 'output', 'select', 'textarea', 'form', 'map', 'meta', 'param', 'slot'],
|
|
39
|
+
pattern: ['input'],
|
|
40
|
+
ping: ['a', 'area'],
|
|
41
|
+
placeholder: ['input', 'textarea'],
|
|
42
|
+
poster: ['video'],
|
|
43
|
+
rel: ['a', 'area', 'link'],
|
|
44
|
+
rows: 'textarea',
|
|
45
|
+
rowspan: ['td', 'th'],
|
|
46
|
+
size: ['input', 'select'],
|
|
47
|
+
span: ['col', 'colgroup'],
|
|
48
|
+
src: ['audio', 'embed', 'iframe', 'img', 'input', 'script', 'source', 'track', 'video'],
|
|
49
|
+
start: 'ol',
|
|
50
|
+
tabindex: null,
|
|
51
|
+
type: ['a', 'link', 'button', 'embed', 'object', 'script', 'source', 'style', 'input', 'menu', 'menuitem', 'ol'],
|
|
52
|
+
value: ['button', 'input', 'li'],
|
|
53
|
+
width: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video']
|
|
54
|
+
};
|
|
9
55
|
/** Removes empty attributes */
|
|
10
56
|
|
|
11
57
|
function removeEmptyAttributes(tree) {
|
|
@@ -17,12 +63,10 @@ function removeEmptyAttributes(tree) {
|
|
|
17
63
|
Object.entries(node.attrs).forEach(([attrName, attrValue]) => {
|
|
18
64
|
const attrNameLower = attrName.toLowerCase();
|
|
19
65
|
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (attrValue === '' || (attrValue || '').match(/^\s+$/)) {
|
|
25
|
-
delete node.attrs[attrName];
|
|
66
|
+
if (attrNameLower.slice(0, 2).toLowerCase() === 'on' && attrName.length >= 5 || Object.hasOwnProperty.call(safeToRemoveAttrs, attrNameLower) && (safeToRemoveAttrs[attrNameLower] === null || safeToRemoveAttrs[attrNameLower].includes(node.tag))) {
|
|
67
|
+
if (attrValue === '' || (attrValue || '').match(/^\s+$/)) {
|
|
68
|
+
delete node.attrs[attrName];
|
|
69
|
+
}
|
|
26
70
|
}
|
|
27
71
|
});
|
|
28
72
|
return node;
|
|
@@ -72,8 +72,47 @@ const redundantAttributes = {
|
|
|
72
72
|
'iframe': {
|
|
73
73
|
'loading': 'eager'
|
|
74
74
|
}
|
|
75
|
+
}; // See: https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#missing-value-default
|
|
76
|
+
|
|
77
|
+
const canBeReplacedWithEmptyStringAttributes = {
|
|
78
|
+
audio: {
|
|
79
|
+
// https://html.spec.whatwg.org/#attr-media-preload
|
|
80
|
+
preload: 'auto'
|
|
81
|
+
},
|
|
82
|
+
video: {
|
|
83
|
+
preload: 'auto'
|
|
84
|
+
},
|
|
85
|
+
form: {
|
|
86
|
+
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofilling-form-controls:-the-autocomplete-attribute
|
|
87
|
+
autocomplete: 'on'
|
|
88
|
+
},
|
|
89
|
+
img: {
|
|
90
|
+
// https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decoding
|
|
91
|
+
decoding: 'auto'
|
|
92
|
+
},
|
|
93
|
+
track: {
|
|
94
|
+
// https://html.spec.whatwg.org/multipage/media.html#htmltrackelement
|
|
95
|
+
kind: 'subtitles'
|
|
96
|
+
},
|
|
97
|
+
textarea: {
|
|
98
|
+
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-wrap
|
|
99
|
+
wrap: 'soft'
|
|
100
|
+
},
|
|
101
|
+
area: {
|
|
102
|
+
// https://html.spec.whatwg.org/multipage/image-maps.html#attr-area-shape
|
|
103
|
+
shape: 'rect'
|
|
104
|
+
},
|
|
105
|
+
button: {
|
|
106
|
+
// https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type
|
|
107
|
+
type: 'submit'
|
|
108
|
+
},
|
|
109
|
+
input: {
|
|
110
|
+
// https://html.spec.whatwg.org/multipage/input.html#states-of-the-type-attribute
|
|
111
|
+
type: 'text'
|
|
112
|
+
}
|
|
75
113
|
};
|
|
76
114
|
const tagsHaveRedundantAttributes = new Set(Object.keys(redundantAttributes));
|
|
115
|
+
const tagsHaveMissingValueDefaultAttributes = new Set(Object.keys(canBeReplacedWithEmptyStringAttributes));
|
|
77
116
|
/** Removes redundant attributes */
|
|
78
117
|
|
|
79
118
|
function removeRedundantAttributes(tree) {
|
|
@@ -82,25 +121,41 @@ function removeRedundantAttributes(tree) {
|
|
|
82
121
|
return node;
|
|
83
122
|
}
|
|
84
123
|
|
|
85
|
-
if (!tagsHaveRedundantAttributes.has(node.tag)) {
|
|
86
|
-
return node;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const tagRedundantAttributes = redundantAttributes[node.tag];
|
|
90
124
|
node.attrs = node.attrs || {};
|
|
91
125
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
126
|
+
if (tagsHaveRedundantAttributes.has(node.tag)) {
|
|
127
|
+
const tagRedundantAttributes = redundantAttributes[node.tag];
|
|
128
|
+
|
|
129
|
+
for (const redundantAttributeName of Object.keys(tagRedundantAttributes)) {
|
|
130
|
+
let tagRedundantAttributeValue = tagRedundantAttributes[redundantAttributeName];
|
|
131
|
+
let isRemove = false;
|
|
132
|
+
|
|
133
|
+
if (typeof tagRedundantAttributeValue === 'function') {
|
|
134
|
+
isRemove = tagRedundantAttributeValue(node);
|
|
135
|
+
} else if (node.attrs[redundantAttributeName] === tagRedundantAttributeValue) {
|
|
136
|
+
isRemove = true;
|
|
137
|
+
}
|
|
95
138
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
isRemove = true;
|
|
139
|
+
if (isRemove) {
|
|
140
|
+
delete node.attrs[redundantAttributeName];
|
|
141
|
+
}
|
|
100
142
|
}
|
|
143
|
+
}
|
|
101
144
|
|
|
102
|
-
|
|
103
|
-
|
|
145
|
+
if (tagsHaveMissingValueDefaultAttributes.has(node.tag)) {
|
|
146
|
+
const tagMissingValueDefaultAttributes = canBeReplacedWithEmptyStringAttributes[node.tag];
|
|
147
|
+
|
|
148
|
+
for (const canBeReplacedWithEmptyStringAttributeName of Object.keys(tagMissingValueDefaultAttributes)) {
|
|
149
|
+
let tagMissingValueDefaultAttribute = tagMissingValueDefaultAttributes[canBeReplacedWithEmptyStringAttributeName];
|
|
150
|
+
let isReplace = false;
|
|
151
|
+
|
|
152
|
+
if (node.attrs[canBeReplacedWithEmptyStringAttributeName] === tagMissingValueDefaultAttribute) {
|
|
153
|
+
isReplace = true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (isReplace) {
|
|
157
|
+
node.attrs[canBeReplacedWithEmptyStringAttributeName] = '';
|
|
158
|
+
}
|
|
104
159
|
}
|
|
105
160
|
}
|
|
106
161
|
|
package/lib/presets/safe.js
CHANGED
|
@@ -27,14 +27,19 @@ var _default = {
|
|
|
27
27
|
minifyJson: {},
|
|
28
28
|
minifySvg: {
|
|
29
29
|
plugins: [{
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
name: 'preset-default',
|
|
31
|
+
params: {
|
|
32
|
+
overrides: {
|
|
33
|
+
collapseGroups: false,
|
|
34
|
+
convertShapeToPath: false
|
|
35
|
+
}
|
|
36
|
+
}
|
|
33
37
|
}]
|
|
34
38
|
},
|
|
35
39
|
minifyConditionalComments: false,
|
|
36
|
-
removeEmptyAttributes: true,
|
|
37
40
|
removeRedundantAttributes: false,
|
|
41
|
+
normalizeAttributeValues: true,
|
|
42
|
+
removeEmptyAttributes: true,
|
|
38
43
|
removeComments: 'safe',
|
|
39
44
|
removeAttributeQuotes: false,
|
|
40
45
|
sortAttributesWithLists: 'alphabetical',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "htmlnano",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Modular HTML minifier, built on top of the PostHTML",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "Kirill Maltsev <maltsevkirill@gmail.com>",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"lint": "eslint *.js lib/*.es6 lib/modules/*.es6 lib/presets/*.es6 test/",
|
|
11
11
|
"pretest": "npm run lint && npm run compile",
|
|
12
12
|
"test": ":",
|
|
13
|
-
"posttest": "mocha --require @babel/register --recursive --check-leaks --globals addresses",
|
|
13
|
+
"posttest": "mocha --timeout 5000 --require @babel/register --recursive --check-leaks --globals addresses",
|
|
14
14
|
"prepare": "npm run compile",
|
|
15
15
|
"release:patch": "release-it patch -n"
|
|
16
16
|
},
|
|
@@ -39,26 +39,28 @@
|
|
|
39
39
|
]
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
42
|
+
"cosmiconfig": "^7.0.1",
|
|
43
|
+
"cssnano": "^5.0.8",
|
|
44
|
+
"postcss": "^8.3.6",
|
|
45
|
+
"posthtml": "^0.16.5",
|
|
46
|
+
"purgecss": "^4.0.0",
|
|
45
47
|
"relateurl": "^0.2.7",
|
|
46
|
-
"srcset": "^
|
|
47
|
-
"svgo": "^
|
|
48
|
-
"terser": "^5.
|
|
48
|
+
"srcset": "^4.0.0",
|
|
49
|
+
"svgo": "^2.6.1",
|
|
50
|
+
"terser": "^5.8.0",
|
|
49
51
|
"timsort": "^0.3.0",
|
|
50
52
|
"uncss": "^0.17.3"
|
|
51
53
|
},
|
|
52
54
|
"devDependencies": {
|
|
53
|
-
"@babel/cli": "^7.
|
|
54
|
-
"@babel/core": "^7.
|
|
55
|
-
"@babel/preset-env": "^7.
|
|
56
|
-
"@babel/register": "^7.
|
|
55
|
+
"@babel/cli": "^7.15.7",
|
|
56
|
+
"@babel/core": "^7.15.5",
|
|
57
|
+
"@babel/preset-env": "^7.15.6",
|
|
58
|
+
"@babel/register": "^7.15.3",
|
|
57
59
|
"babel-eslint": "^10.1.0",
|
|
58
|
-
"eslint": "^7.
|
|
59
|
-
"expect": "^
|
|
60
|
-
"mocha": "^
|
|
61
|
-
"release-it": "^14.5
|
|
60
|
+
"eslint": "^7.32.0",
|
|
61
|
+
"expect": "^27.2.0",
|
|
62
|
+
"mocha": "^9.1.0",
|
|
63
|
+
"release-it": "^14.11.5",
|
|
62
64
|
"rimraf": "^3.0.2"
|
|
63
65
|
},
|
|
64
66
|
"repository": {
|
package/test.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
const htmlnano = require('.');
|
|
2
|
-
const
|
|
2
|
+
// const posthtml = require('posthtml');
|
|
3
|
+
const safePreset = require('./lib/presets/safe');
|
|
4
|
+
// const options = {
|
|
3
5
|
// minifySvg: false,
|
|
4
6
|
// minifyJs: false,
|
|
5
|
-
};
|
|
7
|
+
// };
|
|
6
8
|
// // posthtml, posthtml-render, and posthtml-parse options
|
|
7
9
|
// const postHtmlOptions = {
|
|
8
10
|
// sync: true, // https://github.com/posthtml/posthtml#usage
|
|
@@ -10,26 +12,33 @@ const options = {
|
|
|
10
12
|
// quoteAllAttributes: false, // https://github.com/posthtml/posthtml-render#options
|
|
11
13
|
// };
|
|
12
14
|
|
|
15
|
+
// const html = `
|
|
16
|
+
// <!doctype html>
|
|
17
|
+
// <html lang="en">
|
|
18
|
+
// <head>
|
|
19
|
+
// <meta charset="utf-8">
|
|
20
|
+
// <title></title>
|
|
21
|
+
// <script class="fob">alert(1)</script>
|
|
22
|
+
// <script>alert(2)</script>
|
|
23
|
+
// </head>
|
|
24
|
+
// <body>
|
|
25
|
+
// <script>alert(3)</script>
|
|
26
|
+
// <script>alert(4)</script>
|
|
27
|
+
// </body>
|
|
28
|
+
// </html>
|
|
29
|
+
// `;
|
|
30
|
+
|
|
31
|
+
const options = {
|
|
32
|
+
minifySvg: safePreset.minifySvg,
|
|
33
|
+
};
|
|
13
34
|
const html = `
|
|
14
|
-
|
|
15
|
-
<html lang="en">
|
|
16
|
-
<head>
|
|
17
|
-
<meta charset="utf-8">
|
|
18
|
-
<title></title>
|
|
19
|
-
<script class="fob">alert(1)</script>
|
|
20
|
-
<script>alert(2)</script>
|
|
21
|
-
</head>
|
|
22
|
-
<body>
|
|
23
|
-
<script>alert(3)</script>
|
|
24
|
-
<script>alert(4)</script>
|
|
25
|
-
</body>
|
|
26
|
-
</html>
|
|
35
|
+
<input type="text" class="form-control" name="testInput" autofocus="" autocomplete="off" id="testId"><a id="testId" href="#" class="testClass"></a><img width="20" src="../images/image.png" height="40" alt="image" class="cls" id="id2">
|
|
27
36
|
`;
|
|
28
37
|
|
|
29
38
|
htmlnano
|
|
30
39
|
// "preset" arg might be skipped (see "Presets" section below for more info)
|
|
31
40
|
// "postHtmlOptions" arg might be skipped
|
|
32
|
-
.process(html
|
|
41
|
+
.process(html)
|
|
33
42
|
.then(function (result) {
|
|
34
43
|
// result.html is minified
|
|
35
44
|
console.log(result.html);
|
package/uncss-fork.patch
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
diff --git a/package.json b/package.json
|
|
2
|
+
index a127c0f..66455bb 100644
|
|
3
|
+
--- a/package.json
|
|
4
|
+
+++ b/package.json
|
|
5
|
+
@@ -49,7 +49,7 @@
|
|
6
|
+
"svgo": "^2.4.0",
|
|
7
|
+
"terser": "^5.7.0",
|
|
8
|
+
"timsort": "^0.3.0",
|
|
9
|
+
- "uncss": "^0.17.3"
|
|
10
|
+
+ "@novaatwarren/uncss": "^0.17.4"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@babel/cli": "^7.14.3",
|