htmlnano 0.2.8 → 0.2.9
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 +21 -1
- package/README.md +11 -31
- package/lib/helpers.js +2 -2
- package/lib/modules/collapseBooleanAttributes.js +11 -9
- package/lib/modules/collapseWhitespace.js +10 -18
- package/lib/modules/deduplicateAttributeValues.js +1 -1
- package/lib/modules/minifyConditionalComments.js +7 -1
- package/lib/modules/minifyCss.js +28 -2
- package/lib/modules/minifyJs.js +48 -37
- package/lib/modules/minifySvg.js +5 -4
- package/lib/modules/minifyUrls.js +4 -5
- package/lib/modules/removeRedundantAttributes.js +12 -4
- package/lib/modules/removeUnusedCss.js +1 -3
- package/lib/modules/sortAttributes.js +1 -6
- package/lib/modules/sortAttributesWithLists.js +1 -6
- package/package.json +11 -12
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file.
|
|
|
3
3
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
## [0.2.9] - 2021-04-11
|
|
7
|
+
### Added
|
|
8
|
+
- `minifyConditionalComment` support `<html>` [#125].
|
|
9
|
+
- Minify JS within `<script type="module">` [#135].
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- `collapseWhitespaces` around comment [#120].
|
|
13
|
+
- handle `CDATA` inside script correctly [#122].
|
|
14
|
+
- Minify SVG correctly [#129].
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- Upgrade to terser@5 (JS minification).
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
6
21
|
## [0.2.8] - 2020-11-15
|
|
7
22
|
### Added
|
|
8
23
|
- [`removeOptionalTags`](https://github.com/posthtml/htmlnano#removeoptionaltags) [#110].
|
|
@@ -168,6 +183,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
168
183
|
- Remove attributes that contains only white spaces.
|
|
169
184
|
|
|
170
185
|
|
|
186
|
+
[0.2.9]: https://github.com/posthtml/htmlnano/compare/0.2.8...0.2.9
|
|
171
187
|
[0.2.8]: https://github.com/posthtml/htmlnano/compare/0.2.7...0.2.8
|
|
172
188
|
[0.2.7]: https://github.com/posthtml/htmlnano/compare/0.2.6...0.2.7
|
|
173
189
|
[0.2.6]: https://github.com/posthtml/htmlnano/compare/0.2.5...0.2.6
|
|
@@ -188,7 +204,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
188
204
|
[0.1.2]: https://github.com/posthtml/htmlnano/compare/0.1.1...0.1.2
|
|
189
205
|
[0.1.1]: https://github.com/posthtml/htmlnano/compare/0.1.0...0.1.1
|
|
190
206
|
|
|
191
|
-
|
|
207
|
+
[#135]: https://github.com/posthtml/htmlnano/issues/135
|
|
208
|
+
[#129]: https://github.com/posthtml/htmlnano/issues/129
|
|
209
|
+
[#125]: https://github.com/posthtml/htmlnano/issues/125
|
|
210
|
+
[#122]: https://github.com/posthtml/htmlnano/issues/122
|
|
211
|
+
[#120]: https://github.com/posthtml/htmlnano/issues/120
|
|
192
212
|
[#119]: https://github.com/posthtml/htmlnano/issues/119
|
|
193
213
|
[#117]: https://github.com/posthtml/htmlnano/issues/117
|
|
194
214
|
[#116]: https://github.com/posthtml/htmlnano/issues/116
|
package/README.md
CHANGED
|
@@ -9,15 +9,15 @@ Modular HTML minifier, built on top of the [PostHTML](https://github.com/posthtm
|
|
|
9
9
|
|
|
10
10
|
## [Benchmark](https://github.com/maltsev/html-minifiers-benchmark/blob/master/README.md)
|
|
11
11
|
[html-minifier@4.0.0]: https://www.npmjs.com/package/html-minifier
|
|
12
|
-
[htmlnano@0.2.
|
|
12
|
+
[htmlnano@0.2.8]: https://www.npmjs.com/package/htmlnano
|
|
13
13
|
|
|
14
|
-
| Website | Source (KB) | [html-minifier@4.0.0] | [htmlnano@0.2.
|
|
14
|
+
| Website | Source (KB) | [html-minifier@4.0.0] | [htmlnano@0.2.8] |
|
|
15
15
|
|---------|------------:|----------------:|-----------:|
|
|
16
|
-
| [stackoverflow.blog](https://stackoverflow.blog/) |
|
|
17
|
-
| [github.com](https://github.com/) |
|
|
18
|
-
| [en.wikipedia.org](https://en.wikipedia.org/wiki/Main_Page) |
|
|
19
|
-
| [npmjs.com](https://www.npmjs.com/features) |
|
|
20
|
-
| **Avg. minify rate** | 0% | **
|
|
16
|
+
| [stackoverflow.blog](https://stackoverflow.blog/) | 78 | 72 | 66 |
|
|
17
|
+
| [github.com](https://github.com/) | 215 | 187 | 177 |
|
|
18
|
+
| [en.wikipedia.org](https://en.wikipedia.org/wiki/Main_Page) | 78 | 73 | 72 |
|
|
19
|
+
| [npmjs.com](https://www.npmjs.com/features) | 29 | 25 | 25 |
|
|
20
|
+
| **Avg. minify rate** | 0% | **10%** | **13%** |
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
## Usage
|
|
@@ -207,7 +207,7 @@ Collapses redundant white spaces (including new lines). It doesn’t affect whit
|
|
|
207
207
|
|
|
208
208
|
##### Side effects
|
|
209
209
|
|
|
210
|
-
*all*
|
|
210
|
+
*all*
|
|
211
211
|
`<i>hello</i> <i>world</i>` or `<i>hello</i><br><i>world</i>` after minification will be rendered as `helloworld`.
|
|
212
212
|
To prevent that use either the default `conservative` option, or the `aggressive` option.
|
|
213
213
|
|
|
@@ -216,9 +216,9 @@ Source:
|
|
|
216
216
|
```html
|
|
217
217
|
<div>
|
|
218
218
|
hello world!
|
|
219
|
-
|
|
219
|
+
<a href="#">answer</a>
|
|
220
220
|
<style>div { color: red; } </style>
|
|
221
|
-
|
|
221
|
+
<main></main>
|
|
222
222
|
</div>
|
|
223
223
|
```
|
|
224
224
|
|
|
@@ -550,27 +550,7 @@ Source:
|
|
|
550
550
|
Minified:
|
|
551
551
|
|
|
552
552
|
```html
|
|
553
|
-
<!--[if lte IE 7]><style>.title
|
|
554
|
-
```
|
|
555
|
-
|
|
556
|
-
##### Notice
|
|
557
|
-
|
|
558
|
-
Due to [the limitation of PostHTML](https://github.com/posthtml/posthtml-parser/issues/9) (which is actually a issue from upstream [htmlparser2](https://github.com/fb55/htmlparser2/pull/146)), following html snippet is not supported:
|
|
559
|
-
|
|
560
|
-
```html
|
|
561
|
-
<!--[if lt IE 7]><html class="no-js ie6"><![endif]-->
|
|
562
|
-
<!--[if IE 7]><html class="no-js ie7"><![endif]-->
|
|
563
|
-
<!--[if IE 8]><html class="no-js ie8"><![endif]-->
|
|
564
|
-
<!--[if gt IE 8]><!--><html class="no-js"><!--<![endif]-->
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
Which will result in:
|
|
568
|
-
|
|
569
|
-
```html
|
|
570
|
-
<!--[if lt IE 7]><html class="no-js ie6"></html><![endif]-->
|
|
571
|
-
<!--[if IE 7]><html class="no-js ie7"></html><![endif]-->
|
|
572
|
-
<!--[if IE 8]><html class="no-js ie8"></html><![endif]-->
|
|
573
|
-
<!--[if gt IE 8]><!--><html class="no-js"></html><!--<![endif]-->
|
|
553
|
+
<!--[if lte IE 7]><style>.title{color:red}</style><![endif]-->
|
|
574
554
|
```
|
|
575
555
|
|
|
576
556
|
### removeRedundantAttributes
|
package/lib/helpers.js
CHANGED
|
@@ -25,11 +25,11 @@ function isAmpBoilerplate(node) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
function isComment(content) {
|
|
28
|
-
return (content || '').trim().
|
|
28
|
+
return (content || '').trim().startsWith('<!--');
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
function isConditionalComment(content) {
|
|
32
|
-
return (content || '').trim().
|
|
32
|
+
return (content || '').trim().startsWith('<!--[if');
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
function isStyleNode(node) {
|
|
@@ -9,15 +9,17 @@ const htmlBooleanAttributes = new Set(['allowfullscreen', 'allowpaymentrequest',
|
|
|
9
9
|
const amphtmlBooleanAttributes = new Set(['⚡', 'amp', '⚡4ads', 'amp4ads', '⚡4email', 'amp4email', 'amp-custom', 'amp-boilerplate', 'amp4ads-boilerplate', 'amp4email-boilerplate', 'allow-blocked-ranges', 'amp-access-hide', 'amp-access-template', 'amp-keyframes', 'animate', 'arrows', 'data-block-on-consent', 'data-enable-refresh', 'data-multi-size', 'date-template', 'disable-double-tap', 'disable-session-states', 'disableremoteplayback', 'dots', 'expand-single-section', 'expanded', 'fallback', 'first', 'fullscreen', 'inline', 'lightbox', 'noaudio', 'noautoplay', 'noloading', 'once', 'open-after-clear', 'open-after-select', 'open-button', 'placeholder', 'preload', 'reset-on-refresh', 'reset-on-resize', 'resizable', 'rotate-to-fullscreen', 'second', 'standalone', 'stereo', 'submit-error', 'submit-success', 'submitting', 'subscriptions-actions', 'subscriptions-dialog']);
|
|
10
10
|
|
|
11
11
|
function collapseBooleanAttributes(tree, options, moduleOptions) {
|
|
12
|
-
tree.
|
|
13
|
-
attrs
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (!node.tag) {
|
|
17
|
-
continue;
|
|
18
|
-
}
|
|
12
|
+
tree.walk(node => {
|
|
13
|
+
if (!node.attrs) {
|
|
14
|
+
return node;
|
|
15
|
+
}
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
if (!node.tag) {
|
|
18
|
+
return node;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
for (const attrName of Object.keys(node.attrs)) {
|
|
22
|
+
if (attrName === 'visible' && node.tag.startsWith('a-')) {
|
|
21
23
|
continue;
|
|
22
24
|
}
|
|
23
25
|
|
|
@@ -25,7 +27,7 @@ function collapseBooleanAttributes(tree, options, moduleOptions) {
|
|
|
25
27
|
node.attrs[attrName] = true;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
if (moduleOptions.amphtml && node.attrs[attrName] === ''
|
|
30
|
+
if (moduleOptions.amphtml && amphtmlBooleanAttributes.has(attrName) && node.attrs[attrName] === '') {
|
|
29
31
|
node.attrs[attrName] = true;
|
|
30
32
|
} // collapse crossorigin attributes
|
|
31
33
|
// Specification: https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cors-settings-attributes
|
|
@@ -13,8 +13,7 @@ const noTrimWhitespacesArroundElements = new Set([// non-empty tags that will ma
|
|
|
13
13
|
'comment', 'img', 'input', 'wbr']);
|
|
14
14
|
const noTrimWhitespacesInsideElements = new Set([// non-empty tags that will maintain whitespace within them
|
|
15
15
|
'a', 'abbr', 'acronym', 'b', 'big', 'del', 'em', 'font', 'i', 'ins', 'kbd', 'mark', 'nobr', 'rp', 's', 'samp', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'time', 'tt', 'u', 'var']);
|
|
16
|
-
const whitespacePattern = /\s
|
|
17
|
-
const onlyWhitespacePattern = /^\s+$/;
|
|
16
|
+
const whitespacePattern = /\s+/g;
|
|
18
17
|
const NONE = '';
|
|
19
18
|
const SINGLE_SPACE = ' ';
|
|
20
19
|
const validOptions = ['all', 'aggressive', 'conservative'];
|
|
@@ -23,7 +22,7 @@ const validOptions = ['all', 'aggressive', 'conservative'];
|
|
|
23
22
|
function collapseWhitespace(tree, options, collapseType, tag) {
|
|
24
23
|
collapseType = validOptions.includes(collapseType) ? collapseType : 'conservative';
|
|
25
24
|
tree.forEach((node, index) => {
|
|
26
|
-
if (typeof node === 'string'
|
|
25
|
+
if (typeof node === 'string') {
|
|
27
26
|
const prevNode = tree[index - 1];
|
|
28
27
|
const nextNode = tree[index + 1];
|
|
29
28
|
const prevNodeTag = prevNode && prevNode.tag;
|
|
@@ -54,25 +53,18 @@ function collapseRedundantWhitespaces(text, collapseType, shouldTrim = false, cu
|
|
|
54
53
|
return NONE;
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
|
|
56
|
+
if (!(0, _helpers.isComment)(text)) {
|
|
57
|
+
text = text.replace(whitespacePattern, SINGLE_SPACE);
|
|
58
|
+
}
|
|
58
59
|
|
|
59
60
|
if (shouldTrim) {
|
|
60
61
|
if (collapseType === 'aggressive') {
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (!noTrimWhitespacesArroundElements.has(prevNodeTag) || !noTrimWhitespacesArroundElements.has(nextNodeTag)) {
|
|
65
|
-
text = text.trim();
|
|
66
|
-
}
|
|
67
|
-
} else {
|
|
68
|
-
// text contains whitespaces & non-whitespaces
|
|
69
|
-
if (!noTrimWhitespacesArroundElements.has(prevNodeTag)) {
|
|
70
|
-
text = text.trimStart();
|
|
71
|
-
}
|
|
62
|
+
if (!noTrimWhitespacesArroundElements.has(prevNodeTag)) {
|
|
63
|
+
text = text.trimStart();
|
|
64
|
+
}
|
|
72
65
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
66
|
+
if (!noTrimWhitespacesArroundElements.has(nextNodeTag)) {
|
|
67
|
+
text = text.trimEnd();
|
|
76
68
|
}
|
|
77
69
|
} else {
|
|
78
70
|
// collapseType is 'all', trim spaces
|
|
@@ -23,7 +23,7 @@ function collapseAttributeWhitespace(tree) {
|
|
|
23
23
|
|
|
24
24
|
const attrValues = node.attrs[attrName].split(/\s/);
|
|
25
25
|
const uniqeAttrValues = new Set();
|
|
26
|
-
|
|
26
|
+
const deduplicatedAttrValues = [];
|
|
27
27
|
attrValues.forEach(attrValue => {
|
|
28
28
|
if (!attrValue) {
|
|
29
29
|
// Keep whitespaces
|
|
@@ -47,6 +47,12 @@ async function minifycontentInsideConditionalComments(text, htmlnanoOptions) {
|
|
|
47
47
|
|
|
48
48
|
return Promise.all(matches.map(async match => {
|
|
49
49
|
const result = await _htmlnano.default.process(match[1], htmlnanoOptions, {}, {});
|
|
50
|
-
|
|
50
|
+
let minified = result.html;
|
|
51
|
+
|
|
52
|
+
if (match[1].includes('<html') && minified.includes('</html>')) {
|
|
53
|
+
minified = minified.replace('</html>', '');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return match[0] + minified + match[2];
|
|
51
57
|
}));
|
|
52
58
|
}
|
package/lib/modules/minifyCss.js
CHANGED
|
@@ -34,8 +34,23 @@ function minifyCss(tree, options, cssnanoOptions) {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function processStyleNode(styleNode, cssnanoOptions) {
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
let css = (0, _helpers.extractCssFromStyleNode)(styleNode); // Improve performance by avoiding calling stripCdata again and again
|
|
38
|
+
|
|
39
|
+
let isCdataWrapped = false;
|
|
40
|
+
|
|
41
|
+
if (css.includes('CDATA')) {
|
|
42
|
+
const strippedCss = stripCdata(css);
|
|
43
|
+
isCdataWrapped = css !== strippedCss;
|
|
44
|
+
css = strippedCss;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return _cssnano.default.process(css, postcssOptions, cssnanoOptions).then(result => {
|
|
48
|
+
if (isCdataWrapped) {
|
|
49
|
+
return styleNode.content = ['<![CDATA[' + result + ']]>'];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return styleNode.content = [result.css];
|
|
53
|
+
});
|
|
39
54
|
}
|
|
40
55
|
|
|
41
56
|
function processStyleAttr(node, cssnanoOptions) {
|
|
@@ -49,4 +64,15 @@ function processStyleAttr(node, cssnanoOptions) {
|
|
|
49
64
|
|
|
50
65
|
node.attrs.style = minifiedCss.substring(wrapperStart.length, minifiedCss.length - wrapperEnd.length);
|
|
51
66
|
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function stripCdata(css) {
|
|
70
|
+
const leftStrippedCss = css.replace('<![CDATA[', '');
|
|
71
|
+
|
|
72
|
+
if (leftStrippedCss === css) {
|
|
73
|
+
return css;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const strippedCss = leftStrippedCss.replace(']]>', '');
|
|
77
|
+
return leftStrippedCss === strippedCss ? css : strippedCss;
|
|
52
78
|
}
|
package/lib/modules/minifyJs.js
CHANGED
|
@@ -7,38 +7,40 @@ exports.default = minifyJs;
|
|
|
7
7
|
|
|
8
8
|
var _terser = _interopRequireDefault(require("terser"));
|
|
9
9
|
|
|
10
|
+
var _removeRedundantAttributes = require("./removeRedundantAttributes");
|
|
11
|
+
|
|
10
12
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
11
13
|
|
|
12
14
|
/** Minify JS with Terser */
|
|
13
15
|
function minifyJs(tree, options, terserOptions) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
let promises = [];
|
|
17
|
+
tree.walk(node => {
|
|
18
|
+
if (node.tag && node.tag === 'script') {
|
|
19
|
+
const nodeAttrs = node.attrs || {};
|
|
20
|
+
const mimeType = nodeAttrs.type || 'text/javascript';
|
|
21
|
+
|
|
22
|
+
if (_removeRedundantAttributes.redundantScriptTypes.has(mimeType) || mimeType === 'module') {
|
|
23
|
+
promises.push(processScriptNode(node, terserOptions));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (node.attrs) {
|
|
28
|
+
promises = promises.concat(processNodeWithOnAttrs(node, terserOptions));
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
return node;
|
|
25
32
|
});
|
|
26
|
-
|
|
27
|
-
attrs: true
|
|
28
|
-
}, node => {
|
|
29
|
-
return processNodeWithOnAttrs(node, terserOptions);
|
|
30
|
-
});
|
|
31
|
-
return tree;
|
|
33
|
+
return Promise.all(promises).then(() => tree);
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
function stripCdata(js) {
|
|
35
|
-
const leftStrippedJs = js.replace(/\/\/\s*<!\[CDATA\[/, '');
|
|
37
|
+
const leftStrippedJs = js.replace(/\/\/\s*<!\[CDATA\[/, '').replace(/\/\*\s*<!\[CDATA\[\s*\*\//, '');
|
|
36
38
|
|
|
37
39
|
if (leftStrippedJs === js) {
|
|
38
40
|
return js;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
const strippedJs = leftStrippedJs.replace(/\/\/\s*\]\]>/, '');
|
|
43
|
+
const strippedJs = leftStrippedJs.replace(/\/\/\s*\]\]>/, '').replace(/\/\*\s*\]\]>\s*\*\//, '');
|
|
42
44
|
return leftStrippedJs === strippedJs ? js : strippedJs;
|
|
43
45
|
}
|
|
44
46
|
|
|
@@ -47,38 +49,43 @@ function processScriptNode(scriptNode, terserOptions) {
|
|
|
47
49
|
|
|
48
50
|
if (!js) {
|
|
49
51
|
return scriptNode;
|
|
50
|
-
}
|
|
52
|
+
} // Improve performance by avoiding calling stripCdata again and again
|
|
51
53
|
|
|
52
|
-
const strippedJs = stripCdata(js);
|
|
53
|
-
const isCdataWrapped = js !== strippedJs;
|
|
54
|
-
js = strippedJs;
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
let isCdataWrapped = false;
|
|
57
56
|
|
|
58
|
-
if (
|
|
59
|
-
|
|
57
|
+
if (js.includes('CDATA')) {
|
|
58
|
+
const strippedJs = stripCdata(js);
|
|
59
|
+
isCdataWrapped = js !== strippedJs;
|
|
60
|
+
js = strippedJs;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
return _terser.default.minify(js, terserOptions).then(result => {
|
|
64
|
+
if (result.error) {
|
|
65
|
+
throw new Error(result.error);
|
|
66
|
+
}
|
|
65
67
|
|
|
66
|
-
|
|
68
|
+
if (result.code === undefined) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
67
71
|
|
|
68
|
-
|
|
69
|
-
content = '//<![CDATA[' + content + '//]]>';
|
|
70
|
-
}
|
|
72
|
+
let content = result.code;
|
|
71
73
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
if (isCdataWrapped) {
|
|
75
|
+
content = '/*<![CDATA[*/' + content + '/*]]>*/';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
scriptNode.content = [content];
|
|
79
|
+
});
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
function processNodeWithOnAttrs(node, terserOptions) {
|
|
77
83
|
const jsWrapperStart = 'function _(){';
|
|
78
84
|
const jsWrapperEnd = '}';
|
|
85
|
+
const promises = [];
|
|
79
86
|
|
|
80
87
|
for (const attrName of Object.keys(node.attrs || {})) {
|
|
81
|
-
if (attrName.
|
|
88
|
+
if (!attrName.startsWith('on')) {
|
|
82
89
|
continue;
|
|
83
90
|
} // For example onclick="return false" is valid,
|
|
84
91
|
// but "return false;" is invalid (error: 'return' outside of function)
|
|
@@ -88,11 +95,15 @@ function processNodeWithOnAttrs(node, terserOptions) {
|
|
|
88
95
|
|
|
89
96
|
let wrappedJs = jsWrapperStart + node.attrs[attrName] + jsWrapperEnd;
|
|
90
97
|
|
|
91
|
-
let
|
|
98
|
+
let promise = _terser.default.minify(wrappedJs, terserOptions).then(({
|
|
99
|
+
code
|
|
100
|
+
}) => {
|
|
101
|
+
let minifiedJs = code.substring(jsWrapperStart.length, code.length - jsWrapperEnd.length);
|
|
102
|
+
node.attrs[attrName] = minifiedJs;
|
|
103
|
+
});
|
|
92
104
|
|
|
93
|
-
|
|
94
|
-
node.attrs[attrName] = minifiedJs;
|
|
105
|
+
promises.push(promise);
|
|
95
106
|
}
|
|
96
107
|
|
|
97
|
-
return
|
|
108
|
+
return promises;
|
|
98
109
|
}
|
package/lib/modules/minifySvg.js
CHANGED
|
@@ -7,18 +7,19 @@ exports.default = minifySvg;
|
|
|
7
7
|
|
|
8
8
|
var _svgo = _interopRequireDefault(require("svgo"));
|
|
9
9
|
|
|
10
|
-
var _posthtmlRender = _interopRequireDefault(require("posthtml-render"));
|
|
11
|
-
|
|
12
10
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
13
11
|
|
|
14
12
|
/** Minify SVG with SVGO */
|
|
15
13
|
function minifySvg(tree, options, svgoOptions = {}) {
|
|
16
14
|
let promises = [];
|
|
17
|
-
|
|
15
|
+
const svgo = new _svgo.default(svgoOptions);
|
|
18
16
|
tree.match({
|
|
19
17
|
tag: 'svg'
|
|
20
18
|
}, node => {
|
|
21
|
-
let svgStr = (
|
|
19
|
+
let svgStr = tree.render(node, {
|
|
20
|
+
closingSingleTag: 'slash',
|
|
21
|
+
quoteAllAttributes: true
|
|
22
|
+
});
|
|
22
23
|
let promise = svgo.optimize(svgStr).then(result => {
|
|
23
24
|
node.tag = false;
|
|
24
25
|
node.attrs = {};
|
|
@@ -13,7 +13,6 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|
|
13
13
|
|
|
14
14
|
// Adopts from https://github.com/kangax/html-minifier/blob/51ce10f4daedb1de483ffbcccecc41be1c873da2/src/htmlminifier.js#L209-L221
|
|
15
15
|
const tagsHaveUriValuesForAttributes = new Set(['a', 'area', 'link', 'base', 'img', 'object', 'q', 'blockquote', 'ins', 'form', 'input', 'head', 'script']);
|
|
16
|
-
const REGEXP_TAGS_HAVE_URI_VSLUES_FOR_ATTRIBUTES = new RegExp('^(' + [...tagsHaveUriValuesForAttributes].join('|') + ')$');
|
|
17
16
|
const tagsHasHrefAttributes = new Set(['a', 'area', 'link', 'base']);
|
|
18
17
|
const attributesOfImgTagHasUriValues = new Set(['src', 'longdesc', 'usemap']);
|
|
19
18
|
const attributesOfObjectTagHasUriValues = new Set(['classid', 'codebase', 'data', 'usemap']);
|
|
@@ -76,10 +75,10 @@ function minifyUrls(tree, options, moduleOptions) {
|
|
|
76
75
|
STORED_URL_BASE = urlBase;
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
tree.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (!node.
|
|
78
|
+
tree.walk(node => {
|
|
79
|
+
if (!node.attrs) return node;
|
|
80
|
+
if (!node.tag) return node;
|
|
81
|
+
if (!tagsHaveUriValuesForAttributes.has(node.tag)) return node; // Prevent link[rel=canonical] being processed
|
|
83
82
|
// Can't be excluded by isUriTypeAttribute()
|
|
84
83
|
|
|
85
84
|
if (isLinkRelCanonical(node)) return node;
|
|
@@ -4,8 +4,10 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = removeRedundantAttributes;
|
|
7
|
+
exports.redundantScriptTypes = void 0;
|
|
7
8
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#JavaScript_types
|
|
8
9
|
const redundantScriptTypes = new Set(['application/javascript', 'application/ecmascript', 'application/x-ecmascript', 'application/x-javascript', 'text/javascript', 'text/ecmascript', 'text/javascript1.0', 'text/javascript1.1', 'text/javascript1.2', 'text/javascript1.3', 'text/javascript1.4', 'text/javascript1.5', 'text/jscript', 'text/livescript', 'text/x-ecmascript', 'text/x-javascript']);
|
|
10
|
+
exports.redundantScriptTypes = redundantScriptTypes;
|
|
9
11
|
const redundantAttributes = {
|
|
10
12
|
'form': {
|
|
11
13
|
'method': 'get'
|
|
@@ -71,13 +73,19 @@ const redundantAttributes = {
|
|
|
71
73
|
'loading': 'eager'
|
|
72
74
|
}
|
|
73
75
|
};
|
|
74
|
-
const
|
|
76
|
+
const tagsHaveRedundantAttributes = new Set(Object.keys(redundantAttributes));
|
|
75
77
|
/** Removes redundant attributes */
|
|
76
78
|
|
|
77
79
|
function removeRedundantAttributes(tree) {
|
|
78
|
-
tree.
|
|
79
|
-
tag
|
|
80
|
-
|
|
80
|
+
tree.walk(node => {
|
|
81
|
+
if (!node.tag) {
|
|
82
|
+
return node;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!tagsHaveRedundantAttributes.has(node.tag)) {
|
|
86
|
+
return node;
|
|
87
|
+
}
|
|
88
|
+
|
|
81
89
|
const tagRedundantAttributes = redundantAttributes[node.tag];
|
|
82
90
|
node.attrs = node.attrs || {};
|
|
83
91
|
|
|
@@ -11,8 +11,6 @@ var _uncss = _interopRequireDefault(require("uncss"));
|
|
|
11
11
|
|
|
12
12
|
var _purgecss = _interopRequireDefault(require("purgecss"));
|
|
13
13
|
|
|
14
|
-
var _posthtmlRender = _interopRequireDefault(require("posthtml-render"));
|
|
15
|
-
|
|
16
14
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
17
15
|
|
|
18
16
|
// These options must be set and shouldn't be overriden to ensure uncss doesn't look at linked stylesheets.
|
|
@@ -111,7 +109,7 @@ function runPurgecss(tree, css, userOptions) {
|
|
|
111
109
|
|
|
112
110
|
function removeUnusedCss(tree, options, userOptions) {
|
|
113
111
|
const promises = [];
|
|
114
|
-
const html = userOptions.tool !== 'purgeCSS' &&
|
|
112
|
+
const html = userOptions.tool !== 'purgeCSS' && tree.render(tree);
|
|
115
113
|
tree.walk(node => {
|
|
116
114
|
if ((0, _helpers.isStyleNode)(node)) {
|
|
117
115
|
if (userOptions.tool === 'purgeCSS') {
|
|
@@ -32,12 +32,7 @@ class AttributeTokenChain {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
createSortOrder() {
|
|
35
|
-
let _sortOrder = [];
|
|
36
|
-
|
|
37
|
-
for (const item of this.freqData.entries()) {
|
|
38
|
-
_sortOrder.push(item);
|
|
39
|
-
}
|
|
40
|
-
|
|
35
|
+
let _sortOrder = [...this.freqData.entries()];
|
|
41
36
|
(0, _timsort.sort)(_sortOrder, (a, b) => b[1] - a[1]);
|
|
42
37
|
this.sortOrder = _sortOrder.map(i => i[0]);
|
|
43
38
|
}
|
|
@@ -33,12 +33,7 @@ class AttributeTokenChain {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
createSortOrder() {
|
|
36
|
-
let _sortOrder = [];
|
|
37
|
-
|
|
38
|
-
for (const item of this.freqData.entries()) {
|
|
39
|
-
_sortOrder.push(item);
|
|
40
|
-
}
|
|
41
|
-
|
|
36
|
+
let _sortOrder = [...this.freqData.entries()];
|
|
42
37
|
(0, _timsort.sort)(_sortOrder, (a, b) => b[1] - a[1]);
|
|
43
38
|
this.sortOrder = _sortOrder.map(i => i[0]);
|
|
44
39
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "htmlnano",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
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>",
|
|
@@ -39,27 +39,26 @@
|
|
|
39
39
|
]
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"cssnano": "^4.1.
|
|
43
|
-
"posthtml": "^0.
|
|
44
|
-
"posthtml-render": "^1.3.0",
|
|
42
|
+
"cssnano": "^4.1.11",
|
|
43
|
+
"posthtml": "^0.15.1",
|
|
45
44
|
"purgecss": "^2.3.0",
|
|
46
45
|
"relateurl": "^0.2.7",
|
|
47
46
|
"srcset": "^3.0.0",
|
|
48
47
|
"svgo": "^1.3.2",
|
|
49
|
-
"terser": "^
|
|
48
|
+
"terser": "^5.6.1",
|
|
50
49
|
"timsort": "^0.3.0",
|
|
51
50
|
"uncss": "^0.17.3"
|
|
52
51
|
},
|
|
53
52
|
"devDependencies": {
|
|
54
|
-
"@babel/cli": "^7.
|
|
55
|
-
"@babel/core": "^7.
|
|
56
|
-
"@babel/preset-env": "^7.
|
|
57
|
-
"@babel/register": "^7.
|
|
53
|
+
"@babel/cli": "^7.13.14",
|
|
54
|
+
"@babel/core": "^7.13.15",
|
|
55
|
+
"@babel/preset-env": "^7.13.15",
|
|
56
|
+
"@babel/register": "^7.13.14",
|
|
58
57
|
"babel-eslint": "^10.1.0",
|
|
59
|
-
"eslint": "^7.
|
|
58
|
+
"eslint": "^7.23.0",
|
|
60
59
|
"expect": "^26.6.2",
|
|
61
|
-
"mocha": "^8.2
|
|
62
|
-
"release-it": "^14.
|
|
60
|
+
"mocha": "^8.3.2",
|
|
61
|
+
"release-it": "^14.5.1",
|
|
63
62
|
"rimraf": "^3.0.2"
|
|
64
63
|
},
|
|
65
64
|
"repository": {
|