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 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.6]: https://www.npmjs.com/package/htmlnano
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.6] |
14
+ | Website | Source (KB) | [html-minifier@4.0.0] | [htmlnano@0.2.8] |
15
15
  |---------|------------:|----------------:|-----------:|
16
- | [stackoverflow.blog](https://stackoverflow.blog/) | 76 | 67 | 66 |
17
- | [github.com](https://github.com/) | 131 | 70 | 117 |
18
- | [en.wikipedia.org](https://en.wikipedia.org/wiki/Main_Page) | 80 | 71 | 76 |
19
- | [npmjs.com](https://www.npmjs.com/features) | 31 | 25 | 28 |
20
- | **Avg. minify rate** | 0% | **22%** | **10%** |
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
- \t<a href="#">answer</a>
219
+ <a href="#">answer</a>
220
220
  <style>div { color: red; } </style>
221
- \t\t<main></main>
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 {color:red}</style><![endif]-->
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().search('<!--') === 0;
28
+ return (content || '').trim().startsWith('<!--');
29
29
  }
30
30
 
31
31
  function isConditionalComment(content) {
32
- return (content || '').trim().search(/<!--\[if/) === 0;
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.match({
13
- attrs: true
14
- }, node => {
15
- for (const attrName of Object.keys(node.attrs)) {
16
- if (!node.tag) {
17
- continue;
18
- }
12
+ tree.walk(node => {
13
+ if (!node.attrs) {
14
+ return node;
15
+ }
19
16
 
20
- if (node.tag.search('a-') === 0 && attrName === 'visible') {
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] === '' && amphtmlBooleanAttributes.has(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{1,}/g;
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' && !(0, _helpers.isComment)(node)) {
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
- text = text.replace(whitespacePattern, SINGLE_SPACE);
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 (onlyWhitespacePattern.test(text)) {
62
- // "text" only contains whitespaces. Only trim when both prevNodeTag & nextNodeTag are not "noTrimWhitespacesArroundElement"
63
- // Otherwise the required ONE whitespace will be trimmed
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
- if (!noTrimWhitespacesArroundElements.has(nextNodeTag)) {
74
- text = text.trimEnd();
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
- let deduplicatedAttrValues = [];
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
- return match[0] + result.html + match[2];
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
  }
@@ -34,8 +34,23 @@ function minifyCss(tree, options, cssnanoOptions) {
34
34
  }
35
35
 
36
36
  function processStyleNode(styleNode, cssnanoOptions) {
37
- const css = (0, _helpers.extractCssFromStyleNode)(styleNode);
38
- return _cssnano.default.process(css, postcssOptions, cssnanoOptions).then(result => styleNode.content = [result.css]);
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
  }
@@ -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
- tree.match({
15
- tag: 'script'
16
- }, node => {
17
- const nodeAttrs = node.attrs || {};
18
- const mimeType = nodeAttrs.type || 'text/javascript';
19
-
20
- if (mimeType === 'text/javascript' || mimeType === 'application/javascript') {
21
- return processScriptNode(node, terserOptions);
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
- tree.match({
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
- const result = _terser.default.minify(js, terserOptions);
55
+ let isCdataWrapped = false;
57
56
 
58
- if (result.error) {
59
- throw new Error(result.error);
57
+ if (js.includes('CDATA')) {
58
+ const strippedJs = stripCdata(js);
59
+ isCdataWrapped = js !== strippedJs;
60
+ js = strippedJs;
60
61
  }
61
62
 
62
- if (result.code === undefined) {
63
- return scriptNode;
64
- }
63
+ return _terser.default.minify(js, terserOptions).then(result => {
64
+ if (result.error) {
65
+ throw new Error(result.error);
66
+ }
65
67
 
66
- let content = result.code;
68
+ if (result.code === undefined) {
69
+ return;
70
+ }
67
71
 
68
- if (isCdataWrapped) {
69
- content = '//<![CDATA[' + content + '//]]>';
70
- }
72
+ let content = result.code;
71
73
 
72
- scriptNode.content = [content];
73
- return scriptNode;
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.search('on') !== 0) {
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 wrappedMinifiedJs = _terser.default.minify(wrappedJs, terserOptions).code;
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
- let minifiedJs = wrappedMinifiedJs.substring(jsWrapperStart.length, wrappedMinifiedJs.length - jsWrapperEnd.length);
94
- node.attrs[attrName] = minifiedJs;
105
+ promises.push(promise);
95
106
  }
96
107
 
97
- return node;
108
+ return promises;
98
109
  }
@@ -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
- let svgo = new _svgo.default(svgoOptions);
15
+ const svgo = new _svgo.default(svgoOptions);
18
16
  tree.match({
19
17
  tag: 'svg'
20
18
  }, node => {
21
- let svgStr = (0, _posthtmlRender.default)(node);
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.match({
80
- tag: REGEXP_TAGS_HAVE_URI_VSLUES_FOR_ATTRIBUTES
81
- }, node => {
82
- if (!node.attrs) return node; // Prevent link[rel=canonical] being processed
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 TAG_MATCH_REGEXP = new RegExp('^(' + Object.keys(redundantAttributes).join('|') + ')$');
76
+ const tagsHaveRedundantAttributes = new Set(Object.keys(redundantAttributes));
75
77
  /** Removes redundant attributes */
76
78
 
77
79
  function removeRedundantAttributes(tree) {
78
- tree.match({
79
- tag: TAG_MATCH_REGEXP
80
- }, node => {
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' && (0, _posthtmlRender.default)(tree);
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.8",
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.10",
43
- "posthtml": "^0.13.4",
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": "^4.8.0",
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.12.1",
55
- "@babel/core": "^7.12.3",
56
- "@babel/preset-env": "^7.12.1",
57
- "@babel/register": "^7.12.1",
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.13.0",
58
+ "eslint": "^7.23.0",
60
59
  "expect": "^26.6.2",
61
- "mocha": "^8.2.1",
62
- "release-it": "^14.2.1",
60
+ "mocha": "^8.3.2",
61
+ "release-it": "^14.5.1",
63
62
  "rimraf": "^3.0.2"
64
63
  },
65
64
  "repository": {