htmlnano 1.0.0 → 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.
- package/CHANGELOG.md +55 -2
- package/README.md +12 -893
- 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 +838 -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 +27251 -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_docs/version-2.0.0/010-introduction.md +22 -0
- package/docs/versioned_docs/version-2.0.0/020-usage.md +77 -0
- package/docs/versioned_docs/version-2.0.0/030-config.md +21 -0
- package/docs/versioned_docs/version-2.0.0/040-presets.md +75 -0
- package/docs/versioned_docs/version-2.0.0/050-modules.md +838 -0
- package/docs/versioned_docs/version-2.0.0/060-contribute.md +16 -0
- package/docs/versioned_sidebars/version-1.1.1-sidebars.json +8 -0
- package/docs/versioned_sidebars/version-2.0.0-sidebars.json +8 -0
- package/docs/versions.json +4 -0
- package/lib/helpers.js +19 -1
- package/lib/htmlnano.js +67 -6
- package/lib/modules/collapseAttributeWhitespace.js +63 -7
- package/lib/modules/collapseWhitespace.js +42 -17
- package/lib/modules/minifyCss.js +8 -8
- package/lib/modules/minifyJs.js +9 -10
- package/lib/modules/minifySvg.js +5 -2
- package/lib/modules/minifyUrls.js +71 -29
- package/lib/modules/normalizeAttributeValues.js +48 -0
- package/lib/modules/removeComments.js +25 -1
- package/lib/modules/removeEmptyAttributes.js +53 -8
- package/lib/modules/removeRedundantAttributes.js +69 -14
- package/lib/modules/removeUnusedCss.js +10 -10
- package/lib/presets/max.js +2 -0
- package/lib/presets/safe.js +13 -13
- package/package.json +58 -21
- package/test.js +25 -16
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = normalizeAttributeValues;
|
|
7
|
+
const caseInsensitiveAttributes = {
|
|
8
|
+
autocomplete: ['form'],
|
|
9
|
+
charset: ['meta', 'script'],
|
|
10
|
+
contenteditable: null,
|
|
11
|
+
crossorigin: ['audio', 'img', 'link', 'script', 'video'],
|
|
12
|
+
dir: null,
|
|
13
|
+
draggable: null,
|
|
14
|
+
dropzone: null,
|
|
15
|
+
formmethod: ['button', 'input'],
|
|
16
|
+
inputmode: ['input', 'textarea'],
|
|
17
|
+
kind: ['track'],
|
|
18
|
+
method: ['form'],
|
|
19
|
+
preload: ['audio', 'video'],
|
|
20
|
+
referrerpolicy: ['a', 'area', 'iframe', 'img', 'link'],
|
|
21
|
+
sandbox: ['iframe'],
|
|
22
|
+
spellcheck: null,
|
|
23
|
+
scope: ['th'],
|
|
24
|
+
shape: ['area'],
|
|
25
|
+
sizes: ['link'],
|
|
26
|
+
step: ['input'],
|
|
27
|
+
translate: null,
|
|
28
|
+
type: ['a', 'link', 'button', 'embed', 'object', 'script', 'source', 'style', 'input', 'menu', 'menuitem'],
|
|
29
|
+
wrap: ['textarea']
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function normalizeAttributeValues(tree) {
|
|
33
|
+
tree.walk(node => {
|
|
34
|
+
if (!node.attrs) {
|
|
35
|
+
return node;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
Object.entries(node.attrs).forEach(([attrName, attrValue]) => {
|
|
39
|
+
const attrNameLower = attrName.toLowerCase();
|
|
40
|
+
|
|
41
|
+
if (Object.hasOwnProperty.call(caseInsensitiveAttributes, attrNameLower) && (caseInsensitiveAttributes[attrNameLower] === null || caseInsensitiveAttributes[attrNameLower].includes(node.tag))) {
|
|
42
|
+
node.attrs[attrName] = attrValue.toLowerCase ? attrValue.toLowerCase() : attrValue;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return node;
|
|
46
|
+
});
|
|
47
|
+
return tree;
|
|
48
|
+
}
|
|
@@ -11,7 +11,7 @@ const MATCH_EXCERPT_REGEXP = /<!-- ?more ?-->/i;
|
|
|
11
11
|
/** Removes HTML comments */
|
|
12
12
|
|
|
13
13
|
function removeComments(tree, options, removeType) {
|
|
14
|
-
if (removeType !== 'all' && removeType !== 'safe') {
|
|
14
|
+
if (removeType !== 'all' && removeType !== 'safe' && !isMatcher(removeType)) {
|
|
15
15
|
removeType = 'safe';
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -68,5 +68,29 @@ function isCommentToRemove(text, removeType) {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
if (isMatcher(removeType)) {
|
|
72
|
+
return isMatch(text, removeType);
|
|
73
|
+
}
|
|
74
|
+
|
|
71
75
|
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isMatch(input, matcher) {
|
|
79
|
+
if (matcher instanceof RegExp) {
|
|
80
|
+
return matcher.test(input);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (typeof matcher === 'function') {
|
|
84
|
+
return Boolean(matcher(input));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isMatcher(matcher) {
|
|
91
|
+
if (matcher instanceof RegExp || typeof matcher === 'function') {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return false;
|
|
72
96
|
}
|
|
@@ -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,11 @@ 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
|
-
|
|
25
|
-
delete node.attrs[attrName];
|
|
66
|
+
if (attrNameLower.slice(0, 2).toLowerCase() === 'on' && attrName.length >= 5 // Event Handler
|
|
67
|
+
|| Object.hasOwnProperty.call(safeToRemoveAttrs, attrNameLower) && (safeToRemoveAttrs[attrNameLower] === null || safeToRemoveAttrs[attrNameLower].includes(node.tag))) {
|
|
68
|
+
if (attrValue === '' || (attrValue || '').match(/^\s+$/)) {
|
|
69
|
+
delete node.attrs[attrName];
|
|
70
|
+
}
|
|
26
71
|
}
|
|
27
72
|
});
|
|
28
73
|
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
|
|
|
@@ -7,13 +7,9 @@ exports.default = removeUnusedCss;
|
|
|
7
7
|
|
|
8
8
|
var _helpers = require("../helpers");
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
const uncss = (0, _helpers.optionalRequire)('uncss');
|
|
11
|
+
const purgecss = (0, _helpers.optionalRequire)('purgecss'); // These options must be set and shouldn't be overriden to ensure uncss doesn't look at linked stylesheets.
|
|
11
12
|
|
|
12
|
-
var _purgecss = _interopRequireDefault(require("purgecss"));
|
|
13
|
-
|
|
14
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
15
|
-
|
|
16
|
-
// These options must be set and shouldn't be overriden to ensure uncss doesn't look at linked stylesheets.
|
|
17
13
|
const uncssOptions = {
|
|
18
14
|
ignoreSheets: [/\s*/],
|
|
19
15
|
stylesheets: []
|
|
@@ -43,7 +39,7 @@ function runUncss(html, css, userOptions) {
|
|
|
43
39
|
};
|
|
44
40
|
return new Promise((resolve, reject) => {
|
|
45
41
|
options.raw = css;
|
|
46
|
-
(
|
|
42
|
+
uncss(html, options, (error, output) => {
|
|
47
43
|
if (error) {
|
|
48
44
|
reject(error);
|
|
49
45
|
return;
|
|
@@ -100,7 +96,7 @@ function runPurgecss(tree, css, userOptions) {
|
|
|
100
96
|
extensions: ['html']
|
|
101
97
|
}]
|
|
102
98
|
};
|
|
103
|
-
return new
|
|
99
|
+
return new purgecss.PurgeCSS().purge(options).then(result => {
|
|
104
100
|
return result[0].css;
|
|
105
101
|
});
|
|
106
102
|
}
|
|
@@ -113,9 +109,13 @@ function removeUnusedCss(tree, options, userOptions) {
|
|
|
113
109
|
tree.walk(node => {
|
|
114
110
|
if ((0, _helpers.isStyleNode)(node)) {
|
|
115
111
|
if (userOptions.tool === 'purgeCSS') {
|
|
116
|
-
|
|
112
|
+
if (purgecss) {
|
|
113
|
+
promises.push(processStyleNodePurgeCSS(tree, node, userOptions));
|
|
114
|
+
}
|
|
117
115
|
} else {
|
|
118
|
-
|
|
116
|
+
if (uncss) {
|
|
117
|
+
promises.push(processStyleNodeUnCSS(html, node, userOptions));
|
|
118
|
+
}
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
|
package/lib/presets/max.js
CHANGED
package/lib/presets/safe.js
CHANGED
|
@@ -4,13 +4,10 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
|
-
|
|
8
|
-
var _svgo = require("svgo");
|
|
9
7
|
/**
|
|
10
8
|
* Minify HTML in a safe way without breaking anything.
|
|
11
9
|
*/
|
|
12
10
|
|
|
13
|
-
|
|
14
11
|
var _default = {
|
|
15
12
|
sortAttributes: false,
|
|
16
13
|
collapseAttributeWhitespace: true,
|
|
@@ -20,8 +17,8 @@ var _default = {
|
|
|
20
17
|
collapseWhitespace: 'conservative',
|
|
21
18
|
custom: [],
|
|
22
19
|
deduplicateAttributeValues: true,
|
|
23
|
-
mergeScripts:
|
|
24
|
-
mergeStyles:
|
|
20
|
+
mergeScripts: false,
|
|
21
|
+
mergeStyles: false,
|
|
25
22
|
removeUnusedCss: false,
|
|
26
23
|
minifyCss: {
|
|
27
24
|
preset: 'default'
|
|
@@ -29,17 +26,20 @@ var _default = {
|
|
|
29
26
|
minifyJs: {},
|
|
30
27
|
minifyJson: {},
|
|
31
28
|
minifySvg: {
|
|
32
|
-
plugins:
|
|
33
|
-
name: '
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
plugins: [{
|
|
30
|
+
name: 'preset-default',
|
|
31
|
+
params: {
|
|
32
|
+
overrides: {
|
|
33
|
+
collapseGroups: false,
|
|
34
|
+
convertShapeToPath: false
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}]
|
|
39
38
|
},
|
|
40
39
|
minifyConditionalComments: false,
|
|
41
|
-
removeEmptyAttributes: true,
|
|
42
40
|
removeRedundantAttributes: false,
|
|
41
|
+
normalizeAttributeValues: true,
|
|
42
|
+
removeEmptyAttributes: true,
|
|
43
43
|
removeComments: 'safe',
|
|
44
44
|
removeAttributeQuotes: false,
|
|
45
45
|
sortAttributesWithLists: 'alphabetical',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "htmlnano",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
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,28 +39,65 @@
|
|
|
39
39
|
]
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"purgecss": "^4.0.0",
|
|
46
|
-
"relateurl": "^0.2.7",
|
|
47
|
-
"srcset": "^3.0.0",
|
|
48
|
-
"svgo": "^2.3.0",
|
|
49
|
-
"terser": "^5.6.1",
|
|
50
|
-
"timsort": "^0.3.0",
|
|
51
|
-
"uncss": "^0.17.3"
|
|
42
|
+
"cosmiconfig": "^7.0.1",
|
|
43
|
+
"posthtml": "^0.16.5",
|
|
44
|
+
"timsort": "^0.3.0"
|
|
52
45
|
},
|
|
53
46
|
"devDependencies": {
|
|
54
|
-
"@babel/cli": "^7.
|
|
55
|
-
"@babel/core": "^7.
|
|
56
|
-
"@babel/preset-env": "^7.
|
|
57
|
-
"@babel/register": "^7.
|
|
47
|
+
"@babel/cli": "^7.15.7",
|
|
48
|
+
"@babel/core": "^7.15.5",
|
|
49
|
+
"@babel/preset-env": "^7.15.6",
|
|
50
|
+
"@babel/register": "^7.15.3",
|
|
58
51
|
"babel-eslint": "^10.1.0",
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
52
|
+
"cssnano": "^5.0.11",
|
|
53
|
+
"eslint": "^7.32.0",
|
|
54
|
+
"expect": "^27.2.0",
|
|
55
|
+
"mocha": "^9.1.0",
|
|
56
|
+
"postcss": "^8.3.11",
|
|
57
|
+
"purgecss": "^4.0.3",
|
|
58
|
+
"relateurl": "^0.2.7",
|
|
59
|
+
"release-it": "^14.11.5",
|
|
60
|
+
"rimraf": "^3.0.2",
|
|
61
|
+
"srcset": "^5.0.0",
|
|
62
|
+
"svgo": "^2.8.0",
|
|
63
|
+
"terser": "^5.10.0",
|
|
64
|
+
"uncss": "^0.17.3"
|
|
65
|
+
},
|
|
66
|
+
"peerDependencies": {
|
|
67
|
+
"cssnano": "^5.0.11",
|
|
68
|
+
"postcss": "^8.3.11",
|
|
69
|
+
"purgecss": "^4.0.3",
|
|
70
|
+
"relateurl": "^0.2.7",
|
|
71
|
+
"srcset": "^5.0.0",
|
|
72
|
+
"svgo": "^2.8.0",
|
|
73
|
+
"terser": "^5.10.0",
|
|
74
|
+
"uncss": "^0.17.3"
|
|
75
|
+
},
|
|
76
|
+
"peerDependenciesMeta": {
|
|
77
|
+
"cssnano": {
|
|
78
|
+
"optional": true
|
|
79
|
+
},
|
|
80
|
+
"postcss": {
|
|
81
|
+
"optional": true
|
|
82
|
+
},
|
|
83
|
+
"purgecss": {
|
|
84
|
+
"optional": true
|
|
85
|
+
},
|
|
86
|
+
"relateurl": {
|
|
87
|
+
"optional": true
|
|
88
|
+
},
|
|
89
|
+
"srcset": {
|
|
90
|
+
"optional": true
|
|
91
|
+
},
|
|
92
|
+
"svgo": {
|
|
93
|
+
"optional": true
|
|
94
|
+
},
|
|
95
|
+
"terser": {
|
|
96
|
+
"optional": true
|
|
97
|
+
},
|
|
98
|
+
"uncss": {
|
|
99
|
+
"optional": true
|
|
100
|
+
}
|
|
64
101
|
},
|
|
65
102
|
"repository": {
|
|
66
103
|
"type": "git",
|
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);
|