htmlnano 0.2.4 → 0.2.8

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.
@@ -1,41 +1,42 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
3
  Object.defineProperty(exports, "__esModule", {
4
- value: true
4
+ value: true
5
5
  });
6
6
  exports.default = mergeStyles;
7
7
 
8
- var _helpers = require('../helpers');
8
+ var _helpers = require("../helpers");
9
9
 
10
10
  /* Merge multiple <style> into one */
11
11
  function mergeStyles(tree) {
12
- var styleNodes = {};
13
-
14
- tree.match({ tag: 'style' }, function (node) {
15
- var nodeAttrs = node.attrs || {};
16
- // Skip <style scoped></style>
17
- // https://developer.mozilla.org/en/docs/Web/HTML/Element/style
18
- if (nodeAttrs.scoped !== undefined) {
19
- return node;
20
- }
21
-
22
- if ((0, _helpers.isAmpBoilerplate)(node)) {
23
- return node;
24
- }
25
-
26
- var styleType = nodeAttrs.type || 'text/css';
27
- var styleMedia = nodeAttrs.media || 'all';
28
- var styleKey = styleType + '_' + styleMedia;
29
- if (styleNodes[styleKey]) {
30
- var styleContent = (node.content || []).join(' ');
31
- styleNodes[styleKey].content.push(' ' + styleContent);
32
- return '';
33
- }
34
-
35
- node.content = node.content || [];
36
- styleNodes[styleKey] = node;
37
- return node;
38
- });
39
-
40
- return tree;
12
+ const styleNodes = {};
13
+ tree.match({
14
+ tag: 'style'
15
+ }, node => {
16
+ const nodeAttrs = node.attrs || {}; // Skip <style scoped></style>
17
+ // https://developer.mozilla.org/en/docs/Web/HTML/Element/style
18
+
19
+ if (nodeAttrs.scoped !== undefined) {
20
+ return node;
21
+ }
22
+
23
+ if ((0, _helpers.isAmpBoilerplate)(node)) {
24
+ return node;
25
+ }
26
+
27
+ const styleType = nodeAttrs.type || 'text/css';
28
+ const styleMedia = nodeAttrs.media || 'all';
29
+ const styleKey = styleType + '_' + styleMedia;
30
+
31
+ if (styleNodes[styleKey]) {
32
+ const styleContent = (node.content || []).join(' ');
33
+ styleNodes[styleKey].content.push(' ' + styleContent);
34
+ return '';
35
+ }
36
+
37
+ node.content = node.content || [];
38
+ styleNodes[styleKey] = node;
39
+ return node;
40
+ });
41
+ return tree;
41
42
  }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = minifyConditionalComments;
7
+
8
+ var _htmlnano = _interopRequireDefault(require("../htmlnano"));
9
+
10
+ var _helpers = require("../helpers");
11
+
12
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
+
14
+ // Spec: https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/ms537512(v=vs.85)
15
+ const CONDITIONAL_COMMENT_REGEXP = /(<!--\[if\s+?[^<>[\]]+?]>)([\s\S]+?)(<!\[endif\]-->)/gm;
16
+ /** Minify content inside conditional comments */
17
+
18
+ async function minifyConditionalComments(tree, htmlnanoOptions) {
19
+ // forEach, tree.walk, tree.match just don't support Promise.
20
+ for (let i = 0, len = tree.length; i < len; i++) {
21
+ const node = tree[i];
22
+
23
+ if (typeof node === 'string' && (0, _helpers.isConditionalComment)(node)) {
24
+ tree[i] = await minifycontentInsideConditionalComments(node, htmlnanoOptions);
25
+ }
26
+
27
+ if (node.content && node.content.length) {
28
+ tree[i].content = await minifyConditionalComments(node.content, htmlnanoOptions);
29
+ }
30
+ }
31
+
32
+ return tree;
33
+ }
34
+
35
+ async function minifycontentInsideConditionalComments(text, htmlnanoOptions) {
36
+ let match;
37
+ const matches = []; // FIXME!
38
+ // String#matchAll is supported since Node.js 12
39
+
40
+ while ((match = CONDITIONAL_COMMENT_REGEXP.exec(text)) !== null) {
41
+ matches.push([match[1], match[2], match[3]]);
42
+ }
43
+
44
+ if (!matches.length) {
45
+ return Promise.resolve(text);
46
+ }
47
+
48
+ return Promise.all(matches.map(async match => {
49
+ const result = await _htmlnano.default.process(match[1], htmlnanoOptions, {}, {});
50
+ return match[0] + result.html + match[2];
51
+ }));
52
+ }
@@ -1,60 +1,52 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
3
  Object.defineProperty(exports, "__esModule", {
4
- value: true
4
+ value: true
5
5
  });
6
6
  exports.default = minifyCss;
7
7
 
8
- var _helpers = require('../helpers');
8
+ var _helpers = require("../helpers");
9
9
 
10
- var _cssnano = require('cssnano');
11
-
12
- var _cssnano2 = _interopRequireDefault(_cssnano);
10
+ var _cssnano = _interopRequireDefault(require("cssnano"));
13
11
 
14
12
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
13
 
16
- var postcssOptions = {
17
- // Prevent the following warning from being shown:
18
- // > Without `from` option PostCSS could generate wrong source map and will not find Browserslist config.
19
- // > Set it to CSS file path or to `undefined` to prevent this warning.
20
- from: undefined
14
+ const postcssOptions = {
15
+ // Prevent the following warning from being shown:
16
+ // > Without `from` option PostCSS could generate wrong source map and will not find Browserslist config.
17
+ // > Set it to CSS file path or to `undefined` to prevent this warning.
18
+ from: undefined
21
19
  };
22
-
23
20
  /** Minify CSS with cssnano */
21
+
24
22
  function minifyCss(tree, options, cssnanoOptions) {
25
- var promises = [];
26
- tree.walk(function (node) {
27
- if ((0, _helpers.isStyleNode)(node)) {
28
- promises.push(processStyleNode(node, cssnanoOptions));
29
- } else if (node.attrs && node.attrs.style) {
30
- promises.push(processStyleAttr(node, cssnanoOptions));
31
- }
32
-
33
- return node;
34
- });
35
-
36
- return Promise.all(promises).then(function () {
37
- return tree;
38
- });
23
+ let promises = [];
24
+ tree.walk(node => {
25
+ if ((0, _helpers.isStyleNode)(node)) {
26
+ promises.push(processStyleNode(node, cssnanoOptions));
27
+ } else if (node.attrs && node.attrs.style) {
28
+ promises.push(processStyleAttr(node, cssnanoOptions));
29
+ }
30
+
31
+ return node;
32
+ });
33
+ return Promise.all(promises).then(() => tree);
39
34
  }
40
35
 
41
36
  function processStyleNode(styleNode, cssnanoOptions) {
42
- var css = (0, _helpers.extractCssFromStyleNode)(styleNode);
43
- return _cssnano2.default.process(css, postcssOptions, cssnanoOptions).then(function (result) {
44
- return styleNode.content = [result.css];
45
- });
37
+ const css = (0, _helpers.extractCssFromStyleNode)(styleNode);
38
+ return _cssnano.default.process(css, postcssOptions, cssnanoOptions).then(result => styleNode.content = [result.css]);
46
39
  }
47
40
 
48
41
  function processStyleAttr(node, cssnanoOptions) {
49
- // CSS "color: red;" is invalid. Therefore it should be wrapped inside some selector:
50
- // a{color: red;}
51
- var wrapperStart = 'a{';
52
- var wrapperEnd = '}';
53
- var wrappedStyle = wrapperStart + (node.attrs.style || '') + wrapperEnd;
54
-
55
- return _cssnano2.default.process(wrappedStyle, postcssOptions, cssnanoOptions).then(function (result) {
56
- var minifiedCss = result.css;
57
- // Remove wrapperStart at the start and wrapperEnd at the end of minifiedCss
58
- node.attrs.style = minifiedCss.substring(wrapperStart.length, minifiedCss.length - wrapperEnd.length);
59
- });
42
+ // CSS "color: red;" is invalid. Therefore it should be wrapped inside some selector:
43
+ // a{color: red;}
44
+ const wrapperStart = 'a{';
45
+ const wrapperEnd = '}';
46
+ const wrappedStyle = wrapperStart + (node.attrs.style || '') + wrapperEnd;
47
+ return _cssnano.default.process(wrappedStyle, postcssOptions, cssnanoOptions).then(result => {
48
+ const minifiedCss = result.css; // Remove wrapperStart at the start and wrapperEnd at the end of minifiedCss
49
+
50
+ node.attrs.style = minifiedCss.substring(wrapperStart.length, minifiedCss.length - wrapperEnd.length);
51
+ });
60
52
  }
@@ -1,94 +1,98 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
3
  Object.defineProperty(exports, "__esModule", {
4
- value: true
4
+ value: true
5
5
  });
6
6
  exports.default = minifyJs;
7
7
 
8
- var _terser = require('terser');
9
-
10
- var _terser2 = _interopRequireDefault(_terser);
8
+ var _terser = _interopRequireDefault(require("terser"));
11
9
 
12
10
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
11
 
14
12
  /** Minify JS with Terser */
15
13
  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);
22
+ }
16
23
 
17
- tree.match({ tag: 'script' }, function (node) {
18
- var nodeAttrs = node.attrs || {};
19
- var mimeType = nodeAttrs.type || 'text/javascript';
20
- if (mimeType === 'text/javascript' || mimeType === 'application/javascript') {
21
- return processScriptNode(node, terserOptions);
22
- }
24
+ return node;
25
+ });
26
+ tree.match({
27
+ attrs: true
28
+ }, node => {
29
+ return processNodeWithOnAttrs(node, terserOptions);
30
+ });
31
+ return tree;
32
+ }
23
33
 
24
- return node;
25
- });
34
+ function stripCdata(js) {
35
+ const leftStrippedJs = js.replace(/\/\/\s*<!\[CDATA\[/, '');
26
36
 
27
- tree.match({ attrs: true }, function (node) {
28
- return processNodeWithOnAttrs(node, terserOptions);
29
- });
37
+ if (leftStrippedJs === js) {
38
+ return js;
39
+ }
30
40
 
31
- return tree;
41
+ const strippedJs = leftStrippedJs.replace(/\/\/\s*\]\]>/, '');
42
+ return leftStrippedJs === strippedJs ? js : strippedJs;
32
43
  }
33
44
 
34
45
  function processScriptNode(scriptNode, terserOptions) {
35
- var js = (scriptNode.content || []).join(' ').trim();
36
- if (!js) {
37
- return scriptNode;
38
- }
46
+ let js = (scriptNode.content || []).join('').trim();
39
47
 
40
- var result = _terser2.default.minify(js, terserOptions);
41
- if (result.error) {
42
- throw new Error(result.error);
43
- }
44
- if (result.code === undefined) {
45
- return scriptNode;
46
- }
48
+ if (!js) {
49
+ return scriptNode;
50
+ }
51
+
52
+ const strippedJs = stripCdata(js);
53
+ const isCdataWrapped = js !== strippedJs;
54
+ js = strippedJs;
55
+
56
+ const result = _terser.default.minify(js, terserOptions);
47
57
 
48
- scriptNode.content = [result.code];
58
+ if (result.error) {
59
+ throw new Error(result.error);
60
+ }
49
61
 
62
+ if (result.code === undefined) {
50
63
  return scriptNode;
64
+ }
65
+
66
+ let content = result.code;
67
+
68
+ if (isCdataWrapped) {
69
+ content = '//<![CDATA[' + content + '//]]>';
70
+ }
71
+
72
+ scriptNode.content = [content];
73
+ return scriptNode;
51
74
  }
52
75
 
53
76
  function processNodeWithOnAttrs(node, terserOptions) {
54
- var jsWrapperStart = 'function _(){';
55
- var jsWrapperEnd = '}';
56
-
57
- var _iteratorNormalCompletion = true;
58
- var _didIteratorError = false;
59
- var _iteratorError = undefined;
60
-
61
- try {
62
- for (var _iterator = Object.keys(node.attrs || {})[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
63
- var attrName = _step.value;
64
-
65
- if (attrName.search('on') !== 0) {
66
- continue;
67
- }
68
-
69
- // For example onclick="return false" is valid,
70
- // but "return false;" is invalid (error: 'return' outside of function)
71
- // Therefore the attribute's code should be wrapped inside function:
72
- // "function _(){return false;}"
73
- var wrappedJs = jsWrapperStart + node.attrs[attrName] + jsWrapperEnd;
74
- var wrappedMinifiedJs = _terser2.default.minify(wrappedJs, terserOptions).code;
75
- var minifiedJs = wrappedMinifiedJs.substring(jsWrapperStart.length, wrappedMinifiedJs.length - jsWrapperEnd.length);
76
- node.attrs[attrName] = minifiedJs;
77
- }
78
- } catch (err) {
79
- _didIteratorError = true;
80
- _iteratorError = err;
81
- } finally {
82
- try {
83
- if (!_iteratorNormalCompletion && _iterator.return) {
84
- _iterator.return();
85
- }
86
- } finally {
87
- if (_didIteratorError) {
88
- throw _iteratorError;
89
- }
90
- }
91
- }
77
+ const jsWrapperStart = 'function _(){';
78
+ const jsWrapperEnd = '}';
92
79
 
93
- return node;
80
+ for (const attrName of Object.keys(node.attrs || {})) {
81
+ if (attrName.search('on') !== 0) {
82
+ continue;
83
+ } // For example onclick="return false" is valid,
84
+ // but "return false;" is invalid (error: 'return' outside of function)
85
+ // Therefore the attribute's code should be wrapped inside function:
86
+ // "function _(){return false;}"
87
+
88
+
89
+ let wrappedJs = jsWrapperStart + node.attrs[attrName] + jsWrapperEnd;
90
+
91
+ let wrappedMinifiedJs = _terser.default.minify(wrappedJs, terserOptions).code;
92
+
93
+ let minifiedJs = wrappedMinifiedJs.substring(jsWrapperStart.length, wrappedMinifiedJs.length - jsWrapperEnd.length);
94
+ node.attrs[attrName] = minifiedJs;
95
+ }
96
+
97
+ return node;
94
98
  }
@@ -1,27 +1,33 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
3
  Object.defineProperty(exports, "__esModule", {
4
- value: true
4
+ value: true
5
5
  });
6
6
  exports.default = minifyJson;
7
+
7
8
  /* Minify JSON inside <script> tags */
8
9
  function minifyJson(tree) {
9
- // Match all <script> tags which have JSON mime type
10
- tree.match({ tag: 'script', attrs: { type: /(\/|\+)json/ } }, function (node) {
11
- var content = (node.content || []).join('');
12
- if (!content) {
13
- return node;
14
- }
10
+ // Match all <script> tags which have JSON mime type
11
+ tree.match({
12
+ tag: 'script',
13
+ attrs: {
14
+ type: /(\/|\+)json/
15
+ }
16
+ }, node => {
17
+ let content = (node.content || []).join('');
15
18
 
16
- try {
17
- content = JSON.stringify(JSON.parse(content));
18
- } catch (error) {
19
- return node;
20
- }
19
+ if (!content) {
20
+ return node;
21
+ }
21
22
 
22
- node.content = [content];
23
- return node;
24
- });
23
+ try {
24
+ content = JSON.stringify(JSON.parse(content));
25
+ } catch (error) {
26
+ return node;
27
+ }
25
28
 
26
- return tree;
29
+ node.content = [content];
30
+ return node;
31
+ });
32
+ return tree;
27
33
  }
@@ -1,40 +1,31 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
3
  Object.defineProperty(exports, "__esModule", {
4
- value: true
4
+ value: true
5
5
  });
6
6
  exports.default = minifySvg;
7
7
 
8
- var _svgo = require('svgo');
8
+ var _svgo = _interopRequireDefault(require("svgo"));
9
9
 
10
- var _svgo2 = _interopRequireDefault(_svgo);
11
-
12
- var _posthtmlRender = require('posthtml-render');
13
-
14
- var _posthtmlRender2 = _interopRequireDefault(_posthtmlRender);
10
+ var _posthtmlRender = _interopRequireDefault(require("posthtml-render"));
15
11
 
16
12
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17
13
 
18
14
  /** Minify SVG with SVGO */
19
- function minifySvg(tree, options) {
20
- var svgoOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
21
-
22
- var promises = [];
23
- var svgo = new _svgo2.default(svgoOptions);
24
-
25
- tree.match({ tag: 'svg' }, function (node) {
26
- var svgStr = (0, _posthtmlRender2.default)(node);
27
- var promise = svgo.optimize(svgStr).then(function (result) {
28
- node.tag = false;
29
- node.attrs = {};
30
- node.content = result.data;
31
- });
32
- promises.push(promise);
33
-
34
- return node;
35
- });
36
-
37
- return Promise.all(promises).then(function () {
38
- return tree;
15
+ function minifySvg(tree, options, svgoOptions = {}) {
16
+ let promises = [];
17
+ let svgo = new _svgo.default(svgoOptions);
18
+ tree.match({
19
+ tag: 'svg'
20
+ }, node => {
21
+ let svgStr = (0, _posthtmlRender.default)(node);
22
+ let promise = svgo.optimize(svgStr).then(result => {
23
+ node.tag = false;
24
+ node.attrs = {};
25
+ node.content = result.data;
39
26
  });
27
+ promises.push(promise);
28
+ return node;
29
+ });
30
+ return Promise.all(promises).then(() => tree);
40
31
  }
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = minifyUrls;
7
+
8
+ var _relateurl = _interopRequireDefault(require("relateurl"));
9
+
10
+ var _srcset = _interopRequireDefault(require("srcset"));
11
+
12
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
+
14
+ // Adopts from https://github.com/kangax/html-minifier/blob/51ce10f4daedb1de483ffbcccecc41be1c873da2/src/htmlminifier.js#L209-L221
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
+ const tagsHasHrefAttributes = new Set(['a', 'area', 'link', 'base']);
18
+ const attributesOfImgTagHasUriValues = new Set(['src', 'longdesc', 'usemap']);
19
+ const attributesOfObjectTagHasUriValues = new Set(['classid', 'codebase', 'data', 'usemap']);
20
+
21
+ const isUriTypeAttribute = (tag, attr) => {
22
+ return tagsHasHrefAttributes.has(tag) && attr === 'href' || tag === 'img' && attributesOfImgTagHasUriValues.has(attr) || tag === 'object' && attributesOfObjectTagHasUriValues.has(attr) || tag === 'q' && attr === 'cite' || tag === 'blockquote' && attr === 'cite' || (tag === 'ins' || tag === 'del') && attr === 'cite' || tag === 'form' && attr === 'action' || tag === 'input' && (attr === 'src' || attr === 'usemap') || tag === 'head' && attr === 'profile' || tag === 'script' && (attr === 'src' || attr === 'for') ||
23
+ /**
24
+ * https://html.spec.whatwg.org/#attr-source-src
25
+ *
26
+ * Although most of browsers recommend not to use "src" in <source>,
27
+ * but technically it does comply with HTML Standard.
28
+ */
29
+ tag === 'source' && attr === 'src';
30
+ };
31
+
32
+ const isSrcsetAttribute = (tag, attr) => {
33
+ return tag === 'source' && attr === 'srcset' || tag === 'img' && attr === 'srcset' || tag === 'link' && attr === 'imagesrcset';
34
+ };
35
+
36
+ const processModuleOptions = options => {
37
+ // FIXME!
38
+ // relateurl@1.0.0-alpha only supports URL while stable version (0.2.7) only supports string
39
+ // should convert input into URL instance after relateurl@1 is stable
40
+ if (typeof options === 'string') return options;
41
+ if (options instanceof URL) return options.toString();
42
+ return false;
43
+ };
44
+
45
+ const isLinkRelCanonical = ({
46
+ tag,
47
+ attrs
48
+ }) => {
49
+ // Return false early for non-"link" tag
50
+ if (tag !== 'link') return false;
51
+
52
+ for (const [attrName, attrValue] of Object.entries(attrs)) {
53
+ if (attrName.toLowerCase() === 'rel' && attrValue === 'canonical') return true;
54
+ }
55
+
56
+ return false;
57
+ };
58
+
59
+ let relateUrlInstance;
60
+ let STORED_URL_BASE;
61
+ /** Convert absolute url into relative url */
62
+
63
+ function minifyUrls(tree, options, moduleOptions) {
64
+ const urlBase = processModuleOptions(moduleOptions); // Invalid configuration, return tree directly
65
+
66
+ if (!urlBase) return tree;
67
+ /** Bring up a reusable RelateUrl instances (only once)
68
+ *
69
+ * STORED_URL_BASE is used to invalidate RelateUrl instances,
70
+ * avoiding require.cache acrossing multiple htmlnano instance with different configuration,
71
+ * e.g. unit tests cases.
72
+ */
73
+
74
+ if (!relateUrlInstance || STORED_URL_BASE !== urlBase) {
75
+ relateUrlInstance = new _relateurl.default(urlBase);
76
+ STORED_URL_BASE = urlBase;
77
+ }
78
+
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
83
+ // Can't be excluded by isUriTypeAttribute()
84
+
85
+ if (isLinkRelCanonical(node)) return node;
86
+
87
+ for (const [attrName, attrValue] of Object.entries(node.attrs)) {
88
+ const attrNameLower = attrName.toLowerCase();
89
+
90
+ if (isUriTypeAttribute(node.tag, attrNameLower)) {
91
+ // FIXME!
92
+ // relateurl@1.0.0-alpha only supports URL while stable version (0.2.7) only supports string
93
+ // the WHATWG URL API is very strict while attrValue might not be a valid URL
94
+ // new URL should be used, and relateUrl#relate should be wrapped in try...catch after relateurl@1 is stable
95
+ node.attrs[attrName] = relateUrlInstance.relate(attrValue);
96
+ continue;
97
+ }
98
+
99
+ if (isSrcsetAttribute(node.tag, attrNameLower)) {
100
+ try {
101
+ const parsedSrcset = _srcset.default.parse(attrValue);
102
+
103
+ node.attrs[attrName] = _srcset.default.stringify(parsedSrcset.map(srcset => {
104
+ srcset.url = relateUrlInstance.relate(srcset.url);
105
+ return srcset;
106
+ }));
107
+ } catch (e) {// srcset will throw an Error for invalid srcset.
108
+ }
109
+
110
+ continue;
111
+ }
112
+ }
113
+
114
+ return node;
115
+ });
116
+ return tree;
117
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = removeAttributeQuotes;
7
+
8
+ // Specification: https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
9
+ // See also: https://github.com/posthtml/posthtml-render/pull/30
10
+ // See also: https://github.com/posthtml/htmlnano/issues/6#issuecomment-707105334
11
+
12
+ /** Disable quoteAllAttributes while not overriding the configuration */
13
+ function removeAttributeQuotes(tree) {
14
+ if (tree.options && typeof tree.options.quoteAllAttributes === 'undefined') {
15
+ tree.options.quoteAllAttributes = false;
16
+ }
17
+
18
+ return tree;
19
+ }