htmlnano 0.2.5 → 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 +81 -2
- package/README.md +268 -15
- package/lib/helpers.js +16 -36
- package/lib/htmlnano.js +33 -69
- package/lib/modules/collapseAttributeWhitespace.js +21 -19
- package/lib/modules/collapseBooleanAttributes.js +36 -48
- package/lib/modules/collapseWhitespace.js +58 -32
- package/lib/modules/custom.js +12 -12
- package/lib/modules/deduplicateAttributeValues.js +34 -36
- package/lib/modules/mergeScripts.js +46 -66
- package/lib/modules/mergeStyles.js +33 -32
- package/lib/modules/minifyConditionalComments.js +58 -0
- package/lib/modules/minifyCss.js +59 -41
- package/lib/modules/minifyJs.js +79 -64
- package/lib/modules/minifyJson.js +23 -17
- package/lib/modules/minifySvg.js +20 -28
- package/lib/modules/minifyUrls.js +116 -0
- package/lib/modules/removeAttributeQuotes.js +19 -0
- package/lib/modules/removeComments.js +52 -34
- package/lib/modules/removeEmptyAttributes.js +19 -21
- package/lib/modules/removeOptionalTags.js +220 -0
- package/lib/modules/removeRedundantAttributes.js +89 -66
- package/lib/modules/removeUnusedCss.js +94 -116
- package/lib/modules/sortAttributes.js +115 -0
- package/lib/modules/sortAttributesWithLists.js +138 -0
- package/lib/presets/ampSafe.js +16 -13
- package/lib/presets/max.js +23 -17
- package/lib/presets/safe.js +38 -25
- package/package.json +28 -19
- package/test.js +30 -7
- package/lib/presets/hard.js +0 -26
package/lib/modules/minifyCss.js
CHANGED
|
@@ -1,60 +1,78 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
|
|
4
|
+
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = minifyCss;
|
|
7
7
|
|
|
8
|
-
var _helpers = require(
|
|
8
|
+
var _helpers = require("../helpers");
|
|
9
9
|
|
|
10
|
-
var _cssnano = require(
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
+
});
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
function processStyleAttr(node, cssnanoOptions) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
// CSS "color: red;" is invalid. Therefore it should be wrapped inside some selector:
|
|
58
|
+
// a{color: red;}
|
|
59
|
+
const wrapperStart = 'a{';
|
|
60
|
+
const wrapperEnd = '}';
|
|
61
|
+
const wrappedStyle = wrapperStart + (node.attrs.style || '') + wrapperEnd;
|
|
62
|
+
return _cssnano.default.process(wrappedStyle, postcssOptions, cssnanoOptions).then(result => {
|
|
63
|
+
const minifiedCss = result.css; // Remove wrapperStart at the start and wrapperEnd at the end of minifiedCss
|
|
64
|
+
|
|
65
|
+
node.attrs.style = minifiedCss.substring(wrapperStart.length, minifiedCss.length - wrapperEnd.length);
|
|
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;
|
|
60
78
|
}
|
package/lib/modules/minifyJs.js
CHANGED
|
@@ -1,94 +1,109 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
|
|
4
|
+
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = minifyJs;
|
|
7
7
|
|
|
8
|
-
var _terser = require(
|
|
8
|
+
var _terser = _interopRequireDefault(require("terser"));
|
|
9
9
|
|
|
10
|
-
var
|
|
10
|
+
var _removeRedundantAttributes = require("./removeRedundantAttributes");
|
|
11
11
|
|
|
12
12
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
13
13
|
|
|
14
14
|
/** Minify JS with Terser */
|
|
15
15
|
function minifyJs(tree, options, 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
|
+
}
|
|
16
26
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (mimeType === 'text/javascript' || mimeType === 'application/javascript') {
|
|
21
|
-
return processScriptNode(node, terserOptions);
|
|
22
|
-
}
|
|
27
|
+
if (node.attrs) {
|
|
28
|
+
promises = promises.concat(processNodeWithOnAttrs(node, terserOptions));
|
|
29
|
+
}
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
return node;
|
|
32
|
+
});
|
|
33
|
+
return Promise.all(promises).then(() => tree);
|
|
34
|
+
}
|
|
26
35
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
});
|
|
36
|
+
function stripCdata(js) {
|
|
37
|
+
const leftStrippedJs = js.replace(/\/\/\s*<!\[CDATA\[/, '').replace(/\/\*\s*<!\[CDATA\[\s*\*\//, '');
|
|
30
38
|
|
|
31
|
-
|
|
39
|
+
if (leftStrippedJs === js) {
|
|
40
|
+
return js;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const strippedJs = leftStrippedJs.replace(/\/\/\s*\]\]>/, '').replace(/\/\*\s*\]\]>\s*\*\//, '');
|
|
44
|
+
return leftStrippedJs === strippedJs ? js : strippedJs;
|
|
32
45
|
}
|
|
33
46
|
|
|
34
47
|
function processScriptNode(scriptNode, terserOptions) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
48
|
+
let js = (scriptNode.content || []).join('').trim();
|
|
49
|
+
|
|
50
|
+
if (!js) {
|
|
51
|
+
return scriptNode;
|
|
52
|
+
} // Improve performance by avoiding calling stripCdata again and again
|
|
53
|
+
|
|
39
54
|
|
|
40
|
-
|
|
55
|
+
let isCdataWrapped = false;
|
|
56
|
+
|
|
57
|
+
if (js.includes('CDATA')) {
|
|
58
|
+
const strippedJs = stripCdata(js);
|
|
59
|
+
isCdataWrapped = js !== strippedJs;
|
|
60
|
+
js = strippedJs;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return _terser.default.minify(js, terserOptions).then(result => {
|
|
41
64
|
if (result.error) {
|
|
42
|
-
|
|
65
|
+
throw new Error(result.error);
|
|
43
66
|
}
|
|
67
|
+
|
|
44
68
|
if (result.code === undefined) {
|
|
45
|
-
|
|
69
|
+
return;
|
|
46
70
|
}
|
|
47
71
|
|
|
48
|
-
|
|
72
|
+
let content = result.code;
|
|
49
73
|
|
|
50
|
-
|
|
74
|
+
if (isCdataWrapped) {
|
|
75
|
+
content = '/*<![CDATA[*/' + content + '/*]]>*/';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
scriptNode.content = [content];
|
|
79
|
+
});
|
|
51
80
|
}
|
|
52
81
|
|
|
53
82
|
function processNodeWithOnAttrs(node, terserOptions) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
}
|
|
83
|
+
const jsWrapperStart = 'function _(){';
|
|
84
|
+
const jsWrapperEnd = '}';
|
|
85
|
+
const promises = [];
|
|
86
|
+
|
|
87
|
+
for (const attrName of Object.keys(node.attrs || {})) {
|
|
88
|
+
if (!attrName.startsWith('on')) {
|
|
89
|
+
continue;
|
|
90
|
+
} // For example onclick="return false" is valid,
|
|
91
|
+
// but "return false;" is invalid (error: 'return' outside of function)
|
|
92
|
+
// Therefore the attribute's code should be wrapped inside function:
|
|
93
|
+
// "function _(){return false;}"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
let wrappedJs = jsWrapperStart + node.attrs[attrName] + jsWrapperEnd;
|
|
97
|
+
|
|
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
|
-
|
|
105
|
+
promises.push(promise);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return promises;
|
|
94
109
|
}
|
|
@@ -1,27 +1,33 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
|
|
4
|
+
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = minifyJson;
|
|
7
|
+
|
|
7
8
|
/* Minify JSON inside <script> tags */
|
|
8
9
|
function minifyJson(tree) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return node;
|
|
20
|
-
}
|
|
19
|
+
if (!content) {
|
|
20
|
+
return node;
|
|
21
|
+
}
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
})
|
|
23
|
+
try {
|
|
24
|
+
content = JSON.stringify(JSON.parse(content));
|
|
25
|
+
} catch (error) {
|
|
26
|
+
return node;
|
|
27
|
+
}
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
node.content = [content];
|
|
30
|
+
return node;
|
|
31
|
+
});
|
|
32
|
+
return tree;
|
|
27
33
|
}
|
package/lib/modules/minifySvg.js
CHANGED
|
@@ -1,40 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
|
|
4
|
+
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = minifySvg;
|
|
7
7
|
|
|
8
|
-
var _svgo = require(
|
|
9
|
-
|
|
10
|
-
var _svgo2 = _interopRequireDefault(_svgo);
|
|
11
|
-
|
|
12
|
-
var _posthtmlRender = require('posthtml-render');
|
|
13
|
-
|
|
14
|
-
var _posthtmlRender2 = _interopRequireDefault(_posthtmlRender);
|
|
8
|
+
var _svgo = _interopRequireDefault(require("svgo"));
|
|
15
9
|
|
|
16
10
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
17
11
|
|
|
18
12
|
/** Minify SVG with SVGO */
|
|
19
|
-
function minifySvg(tree, options) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
node.tag = false;
|
|
29
|
-
node.attrs = {};
|
|
30
|
-
node.content = result.data;
|
|
31
|
-
});
|
|
32
|
-
promises.push(promise);
|
|
33
|
-
|
|
34
|
-
return node;
|
|
13
|
+
function minifySvg(tree, options, svgoOptions = {}) {
|
|
14
|
+
let promises = [];
|
|
15
|
+
const svgo = new _svgo.default(svgoOptions);
|
|
16
|
+
tree.match({
|
|
17
|
+
tag: 'svg'
|
|
18
|
+
}, node => {
|
|
19
|
+
let svgStr = tree.render(node, {
|
|
20
|
+
closingSingleTag: 'slash',
|
|
21
|
+
quoteAllAttributes: true
|
|
35
22
|
});
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
23
|
+
let promise = svgo.optimize(svgStr).then(result => {
|
|
24
|
+
node.tag = false;
|
|
25
|
+
node.attrs = {};
|
|
26
|
+
node.content = result.data;
|
|
39
27
|
});
|
|
28
|
+
promises.push(promise);
|
|
29
|
+
return node;
|
|
30
|
+
});
|
|
31
|
+
return Promise.all(promises).then(() => tree);
|
|
40
32
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
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 tagsHasHrefAttributes = new Set(['a', 'area', 'link', 'base']);
|
|
17
|
+
const attributesOfImgTagHasUriValues = new Set(['src', 'longdesc', 'usemap']);
|
|
18
|
+
const attributesOfObjectTagHasUriValues = new Set(['classid', 'codebase', 'data', 'usemap']);
|
|
19
|
+
|
|
20
|
+
const isUriTypeAttribute = (tag, attr) => {
|
|
21
|
+
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') ||
|
|
22
|
+
/**
|
|
23
|
+
* https://html.spec.whatwg.org/#attr-source-src
|
|
24
|
+
*
|
|
25
|
+
* Although most of browsers recommend not to use "src" in <source>,
|
|
26
|
+
* but technically it does comply with HTML Standard.
|
|
27
|
+
*/
|
|
28
|
+
tag === 'source' && attr === 'src';
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const isSrcsetAttribute = (tag, attr) => {
|
|
32
|
+
return tag === 'source' && attr === 'srcset' || tag === 'img' && attr === 'srcset' || tag === 'link' && attr === 'imagesrcset';
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const processModuleOptions = options => {
|
|
36
|
+
// FIXME!
|
|
37
|
+
// relateurl@1.0.0-alpha only supports URL while stable version (0.2.7) only supports string
|
|
38
|
+
// should convert input into URL instance after relateurl@1 is stable
|
|
39
|
+
if (typeof options === 'string') return options;
|
|
40
|
+
if (options instanceof URL) return options.toString();
|
|
41
|
+
return false;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const isLinkRelCanonical = ({
|
|
45
|
+
tag,
|
|
46
|
+
attrs
|
|
47
|
+
}) => {
|
|
48
|
+
// Return false early for non-"link" tag
|
|
49
|
+
if (tag !== 'link') return false;
|
|
50
|
+
|
|
51
|
+
for (const [attrName, attrValue] of Object.entries(attrs)) {
|
|
52
|
+
if (attrName.toLowerCase() === 'rel' && attrValue === 'canonical') return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return false;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
let relateUrlInstance;
|
|
59
|
+
let STORED_URL_BASE;
|
|
60
|
+
/** Convert absolute url into relative url */
|
|
61
|
+
|
|
62
|
+
function minifyUrls(tree, options, moduleOptions) {
|
|
63
|
+
const urlBase = processModuleOptions(moduleOptions); // Invalid configuration, return tree directly
|
|
64
|
+
|
|
65
|
+
if (!urlBase) return tree;
|
|
66
|
+
/** Bring up a reusable RelateUrl instances (only once)
|
|
67
|
+
*
|
|
68
|
+
* STORED_URL_BASE is used to invalidate RelateUrl instances,
|
|
69
|
+
* avoiding require.cache acrossing multiple htmlnano instance with different configuration,
|
|
70
|
+
* e.g. unit tests cases.
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
if (!relateUrlInstance || STORED_URL_BASE !== urlBase) {
|
|
74
|
+
relateUrlInstance = new _relateurl.default(urlBase);
|
|
75
|
+
STORED_URL_BASE = urlBase;
|
|
76
|
+
}
|
|
77
|
+
|
|
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
|
|
82
|
+
// Can't be excluded by isUriTypeAttribute()
|
|
83
|
+
|
|
84
|
+
if (isLinkRelCanonical(node)) return node;
|
|
85
|
+
|
|
86
|
+
for (const [attrName, attrValue] of Object.entries(node.attrs)) {
|
|
87
|
+
const attrNameLower = attrName.toLowerCase();
|
|
88
|
+
|
|
89
|
+
if (isUriTypeAttribute(node.tag, attrNameLower)) {
|
|
90
|
+
// FIXME!
|
|
91
|
+
// relateurl@1.0.0-alpha only supports URL while stable version (0.2.7) only supports string
|
|
92
|
+
// the WHATWG URL API is very strict while attrValue might not be a valid URL
|
|
93
|
+
// new URL should be used, and relateUrl#relate should be wrapped in try...catch after relateurl@1 is stable
|
|
94
|
+
node.attrs[attrName] = relateUrlInstance.relate(attrValue);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (isSrcsetAttribute(node.tag, attrNameLower)) {
|
|
99
|
+
try {
|
|
100
|
+
const parsedSrcset = _srcset.default.parse(attrValue);
|
|
101
|
+
|
|
102
|
+
node.attrs[attrName] = _srcset.default.stringify(parsedSrcset.map(srcset => {
|
|
103
|
+
srcset.url = relateUrlInstance.relate(srcset.url);
|
|
104
|
+
return srcset;
|
|
105
|
+
}));
|
|
106
|
+
} catch (e) {// srcset will throw an Error for invalid srcset.
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return node;
|
|
114
|
+
});
|
|
115
|
+
return tree;
|
|
116
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -1,54 +1,72 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
|
|
4
|
+
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = removeComments;
|
|
7
7
|
|
|
8
|
-
var _helpers = require(
|
|
8
|
+
var _helpers = require("../helpers");
|
|
9
9
|
|
|
10
|
+
const MATCH_EXCERPT_REGEXP = /<!-- ?more ?-->/i;
|
|
10
11
|
/** Removes HTML comments */
|
|
11
|
-
function removeComments(tree, options, removeType) {
|
|
12
|
-
if (removeType !== 'all' && removeType !== 'safe') {
|
|
13
|
-
removeType = 'safe';
|
|
14
|
-
}
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
});
|
|
21
|
-
} else if (isCommentToRemove(node, removeType)) {
|
|
22
|
-
node = '';
|
|
23
|
-
}
|
|
13
|
+
function removeComments(tree, options, removeType) {
|
|
14
|
+
if (removeType !== 'all' && removeType !== 'safe') {
|
|
15
|
+
removeType = 'safe';
|
|
16
|
+
}
|
|
24
17
|
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
tree.walk(node => {
|
|
19
|
+
if (node.contents && node.contents.length) {
|
|
20
|
+
node.contents = node.contents.filter(content => !isCommentToRemove(content, removeType));
|
|
21
|
+
} else if (isCommentToRemove(node, removeType)) {
|
|
22
|
+
node = '';
|
|
23
|
+
}
|
|
27
24
|
|
|
28
|
-
return
|
|
25
|
+
return node;
|
|
26
|
+
});
|
|
27
|
+
return tree;
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
function isCommentToRemove(text, removeType) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
if (typeof text !== 'string') {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
if (!(0, _helpers.isComment)(text)) {
|
|
36
|
+
// Not HTML comment
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (removeType === 'safe') {
|
|
41
|
+
const isNoindex = text === '<!--noindex-->' || text === '<!--/noindex-->'; // Don't remove noindex comments.
|
|
42
|
+
// See: https://yandex.com/support/webmaster/controlling-robot/html.xml
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// Don't remove noindex comments.
|
|
44
|
-
// See: https://yandex.com/support/webmaster/controlling-robot/html.xml
|
|
45
|
-
return false;
|
|
44
|
+
if (isNoindex) {
|
|
45
|
+
return false;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
const isServerSideExclude = text === '<!--sse-->' || text === '<!--/sse-->'; // Don't remove sse comments.
|
|
49
|
+
// See: https://support.cloudflare.com/hc/en-us/articles/200170036-What-does-Server-Side-Excludes-SSE-do-
|
|
50
|
+
|
|
51
|
+
if (isServerSideExclude) {
|
|
52
|
+
return false;
|
|
53
|
+
} // https://en.wikipedia.org/wiki/Conditional_comment
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if ((0, _helpers.isConditionalComment)(text)) {
|
|
57
|
+
return false;
|
|
58
|
+
} // Hexo: https://hexo.io/docs/tag-plugins#Post-Excerpt
|
|
59
|
+
// Hugo: https://gohugo.io/content-management/summaries/#manual-summary-splitting
|
|
60
|
+
// WordPress: https://wordpress.com/support/wordpress-editor/blocks/more-block/2/
|
|
61
|
+
// Jekyll: https://jekyllrb.com/docs/posts/#post-excerpts
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
const isCMSExcerptComment = MATCH_EXCERPT_REGEXP.test(text);
|
|
65
|
+
|
|
66
|
+
if (isCMSExcerptComment) {
|
|
67
|
+
return false;
|
|
51
68
|
}
|
|
69
|
+
}
|
|
52
70
|
|
|
53
|
-
|
|
71
|
+
return true;
|
|
54
72
|
}
|