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.
Files changed (47) hide show
  1. package/CHANGELOG.md +55 -2
  2. package/README.md +12 -893
  3. package/docs/README.md +33 -0
  4. package/docs/babel.config.js +3 -0
  5. package/docs/docs/010-introduction.md +22 -0
  6. package/docs/docs/020-usage.md +77 -0
  7. package/docs/docs/030-config.md +21 -0
  8. package/docs/docs/040-presets.md +75 -0
  9. package/docs/docs/050-modules.md +838 -0
  10. package/docs/docs/060-contribute.md +16 -0
  11. package/docs/docusaurus.config.js +60 -0
  12. package/docs/netlify.toml +4 -0
  13. package/docs/package-lock.json +27251 -0
  14. package/docs/package.json +39 -0
  15. package/docs/sidebars.js +26 -0
  16. package/docs/versioned_docs/version-1.1.1/010-introduction.md +22 -0
  17. package/docs/versioned_docs/version-1.1.1/020-usage.md +77 -0
  18. package/docs/versioned_docs/version-1.1.1/030-config.md +21 -0
  19. package/docs/versioned_docs/version-1.1.1/040-presets.md +75 -0
  20. package/docs/versioned_docs/version-1.1.1/050-modules.md +786 -0
  21. package/docs/versioned_docs/version-1.1.1/060-contribute.md +16 -0
  22. package/docs/versioned_docs/version-2.0.0/010-introduction.md +22 -0
  23. package/docs/versioned_docs/version-2.0.0/020-usage.md +77 -0
  24. package/docs/versioned_docs/version-2.0.0/030-config.md +21 -0
  25. package/docs/versioned_docs/version-2.0.0/040-presets.md +75 -0
  26. package/docs/versioned_docs/version-2.0.0/050-modules.md +838 -0
  27. package/docs/versioned_docs/version-2.0.0/060-contribute.md +16 -0
  28. package/docs/versioned_sidebars/version-1.1.1-sidebars.json +8 -0
  29. package/docs/versioned_sidebars/version-2.0.0-sidebars.json +8 -0
  30. package/docs/versions.json +4 -0
  31. package/lib/helpers.js +19 -1
  32. package/lib/htmlnano.js +67 -6
  33. package/lib/modules/collapseAttributeWhitespace.js +63 -7
  34. package/lib/modules/collapseWhitespace.js +42 -17
  35. package/lib/modules/minifyCss.js +8 -8
  36. package/lib/modules/minifyJs.js +9 -10
  37. package/lib/modules/minifySvg.js +5 -2
  38. package/lib/modules/minifyUrls.js +71 -29
  39. package/lib/modules/normalizeAttributeValues.js +48 -0
  40. package/lib/modules/removeComments.js +25 -1
  41. package/lib/modules/removeEmptyAttributes.js +53 -8
  42. package/lib/modules/removeRedundantAttributes.js +69 -14
  43. package/lib/modules/removeUnusedCss.js +10 -10
  44. package/lib/presets/max.js +2 -0
  45. package/lib/presets/safe.js +13 -13
  46. package/package.json +58 -21
  47. 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
- // Source: https://www.w3.org/TR/html4/sgml/dtd.html#events (Generic Attributes)
8
- const safeToRemoveAttrs = new Set(['id', 'class', 'style', 'title', 'lang', 'dir', 'onclick', 'ondblclick', 'onmousedown', 'onmouseup', 'onmouseover', 'onmousemove', 'onmouseout', 'onkeypress', 'onkeydown', 'onkeyup']);
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 (!safeToRemoveAttrs.has(attrNameLower)) {
21
- return;
22
- }
23
-
24
- if (attrValue === '' || (attrValue || '').match(/^\s+$/)) {
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
- for (const redundantAttributeName of Object.keys(tagRedundantAttributes)) {
93
- let tagRedundantAttributeValue = tagRedundantAttributes[redundantAttributeName];
94
- let isRemove = false;
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
- if (typeof tagRedundantAttributeValue === 'function') {
97
- isRemove = tagRedundantAttributeValue(node);
98
- } else if (node.attrs[redundantAttributeName] === tagRedundantAttributeValue) {
99
- isRemove = true;
139
+ if (isRemove) {
140
+ delete node.attrs[redundantAttributeName];
141
+ }
100
142
  }
143
+ }
101
144
 
102
- if (isRemove) {
103
- delete node.attrs[redundantAttributeName];
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
- var _uncss = _interopRequireDefault(require("uncss"));
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
- (0, _uncss.default)(html, options, (error, output) => {
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 _purgecss.default().purge(options).then(result => {
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
- promises.push(processStyleNodePurgeCSS(tree, node, userOptions));
112
+ if (purgecss) {
113
+ promises.push(processStyleNodePurgeCSS(tree, node, userOptions));
114
+ }
117
115
  } else {
118
- promises.push(processStyleNodeUnCSS(html, node, userOptions));
116
+ if (uncss) {
117
+ promises.push(processStyleNodeUnCSS(html, node, userOptions));
118
+ }
119
119
  }
120
120
  }
121
121
 
@@ -22,6 +22,8 @@ var _default = { ..._safe.default,
22
22
  removeComments: 'all',
23
23
  removeAttributeQuotes: true,
24
24
  removeRedundantAttributes: true,
25
+ mergeScripts: true,
26
+ mergeStyles: true,
25
27
  removeUnusedCss: {},
26
28
  minifyCss: {
27
29
  preset: 'default'
@@ -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: true,
24
- mergeStyles: true,
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: (0, _svgo.extendDefaultPlugins)([{
33
- name: 'collapseGroups',
34
- active: false
35
- }, {
36
- name: 'convertShapeToPath',
37
- convertShapeToPath: false
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": "1.0.0",
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
- "cssnano": "^5.0.0",
43
- "postcss": "^8.2.9",
44
- "posthtml": "^0.15.2",
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.13.14",
55
- "@babel/core": "^7.13.15",
56
- "@babel/preset-env": "^7.13.15",
57
- "@babel/register": "^7.13.14",
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
- "eslint": "^7.24.0",
60
- "expect": "^26.6.2",
61
- "mocha": "^8.3.2",
62
- "release-it": "^14.6.1",
63
- "rimraf": "^3.0.2"
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 options = {
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
- <!doctype html>
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, options)
41
+ .process(html)
33
42
  .then(function (result) {
34
43
  // result.html is minified
35
44
  console.log(result.html);