htmlnano 2.0.3 → 2.1.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.
- package/.eslintignore +3 -2
- package/CHANGELOG.md +43 -3
- package/docs/docs/040-presets.md +4 -4
- package/docs/docs/050-modules.md +1 -1
- package/docs/docs/060-contribute.md +1 -1
- package/docs/docusaurus.config.js +5 -0
- package/docs/package-lock.json +7592 -11615
- package/docs/package.json +4 -3
- package/docs/versioned_docs/version-1.1.1/040-presets.md +4 -4
- package/docs/versioned_docs/version-1.1.1/050-modules.md +1 -2
- package/docs/versioned_docs/version-1.1.1/060-contribute.md +1 -1
- package/docs/versioned_docs/version-2.0.0/040-presets.md +4 -4
- package/docs/versioned_docs/version-2.0.0/050-modules.md +1 -1
- package/docs/versioned_docs/version-2.0.0/060-contribute.md +1 -1
- package/index.cjs +11 -0
- package/index.d.cts +3 -0
- package/index.d.mts +3 -0
- package/index.d.ts +3 -3
- package/index.mjs +2 -0
- package/lib/helpers.cjs +78 -0
- package/lib/helpers.mjs +52 -0
- package/lib/htmlnano.cjs +200 -0
- package/lib/htmlnano.mjs +196 -0
- package/lib/modules/{collapseAttributeWhitespace.js → collapseAttributeWhitespace.cjs} +2 -3
- package/lib/modules/collapseAttributeWhitespace.mjs +104 -0
- package/lib/modules/collapseBooleanAttributes.mjs +175 -0
- package/lib/modules/{collapseWhitespace.js → collapseWhitespace.cjs} +3 -2
- package/lib/modules/collapseWhitespace.mjs +132 -0
- package/lib/modules/custom.mjs +16 -0
- package/lib/modules/{deduplicateAttributeValues.js → deduplicateAttributeValues.cjs} +1 -1
- package/lib/modules/deduplicateAttributeValues.mjs +40 -0
- package/lib/modules/example.cjs +85 -0
- package/lib/modules/example.mjs +75 -0
- package/lib/modules/{mergeScripts.js → mergeScripts.cjs} +3 -1
- package/lib/modules/mergeScripts.mjs +56 -0
- package/lib/modules/{mergeStyles.js → mergeStyles.cjs} +4 -2
- package/lib/modules/mergeStyles.mjs +36 -0
- package/lib/modules/{minifyConditionalComments.js → minifyConditionalComments.cjs} +2 -2
- package/lib/modules/minifyConditionalComments.mjs +49 -0
- package/lib/modules/{minifyCss.js → minifyCss.cjs} +12 -8
- package/lib/modules/minifyCss.mjs +88 -0
- package/lib/modules/{minifyJs.js → minifyJs.cjs} +25 -10
- package/lib/modules/minifyJs.mjs +121 -0
- package/lib/modules/{minifyJson.js → minifyJson.cjs} +4 -0
- package/lib/modules/minifyJson.mjs +21 -0
- package/lib/modules/minifySvg.cjs +37 -0
- package/lib/modules/minifySvg.mjs +30 -0
- package/lib/modules/{minifyUrls.js → minifyUrls.cjs} +7 -8
- package/lib/modules/minifyUrls.mjs +229 -0
- package/lib/modules/{normalizeAttributeValues.js → normalizeAttributeValues.cjs} +0 -5
- package/lib/modules/normalizeAttributeValues.mjs +140 -0
- package/lib/modules/removeAttributeQuotes.mjs +12 -0
- package/lib/modules/{removeComments.js → removeComments.cjs} +1 -1
- package/lib/modules/removeComments.mjs +92 -0
- package/lib/modules/{removeEmptyAttributes.js → removeEmptyAttributes.cjs} +1 -1
- package/lib/modules/removeEmptyAttributes.mjs +121 -0
- package/lib/modules/{removeOptionalTags.js → removeOptionalTags.cjs} +1 -1
- package/lib/modules/removeOptionalTags.mjs +225 -0
- package/lib/modules/{removeRedundantAttributes.js → removeRedundantAttributes.cjs} +1 -2
- package/lib/modules/removeRedundantAttributes.mjs +141 -0
- package/lib/modules/{removeUnusedCss.js → removeUnusedCss.cjs} +12 -13
- package/lib/modules/removeUnusedCss.mjs +122 -0
- package/lib/modules/sortAttributes.mjs +121 -0
- package/lib/modules/{sortAttributesWithLists.js → sortAttributesWithLists.cjs} +1 -1
- package/lib/modules/sortAttributesWithLists.mjs +135 -0
- package/lib/presets/{ampSafe.js → ampSafe.cjs} +4 -9
- package/lib/presets/ampSafe.mjs +11 -0
- package/lib/presets/{max.js → max.cjs} +4 -9
- package/lib/presets/max.mjs +20 -0
- package/lib/presets/{safe.js → safe.cjs} +2 -3
- package/lib/presets/safe.mjs +65 -0
- package/package.json +40 -12
- package/test.js +48 -0
- package/index.js +0 -1
- package/lib/helpers.js +0 -52
- package/lib/htmlnano.js +0 -147
- package/lib/modules/minifySvg.js +0 -38
- /package/lib/modules/{collapseBooleanAttributes.js → collapseBooleanAttributes.cjs} +0 -0
- /package/lib/modules/{custom.js → custom.cjs} +0 -0
- /package/lib/modules/{removeAttributeQuotes.js → removeAttributeQuotes.cjs} +0 -0
- /package/lib/modules/{sortAttributes.js → sortAttributes.cjs} +0 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { attributesWithLists } from './collapseAttributeWhitespace.mjs';
|
|
2
|
+
|
|
3
|
+
/** Deduplicate values inside list-like attributes (e.g. class, rel) */
|
|
4
|
+
export function onAttrs() {
|
|
5
|
+
return (attrs) => {
|
|
6
|
+
const newAttrs = attrs;
|
|
7
|
+
Object.keys(attrs).forEach(attrName => {
|
|
8
|
+
if (! attributesWithLists.has(attrName)) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (typeof attrs[attrName] !== 'string') {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const attrValues = attrs[attrName].split(/\s/);
|
|
17
|
+
const uniqeAttrValues = new Set();
|
|
18
|
+
const deduplicatedAttrValues = [];
|
|
19
|
+
|
|
20
|
+
attrValues.forEach((attrValue) => {
|
|
21
|
+
if (! attrValue) {
|
|
22
|
+
// Keep whitespaces
|
|
23
|
+
deduplicatedAttrValues.push('');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (uniqeAttrValues.has(attrValue)) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
deduplicatedAttrValues.push(attrValue);
|
|
32
|
+
uniqeAttrValues.add(attrValue);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
newAttrs[attrName] = deduplicatedAttrValues.join(' ');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return newAttrs;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = example;
|
|
7
|
+
exports.onAttrs = onAttrs;
|
|
8
|
+
exports.onContent = onContent;
|
|
9
|
+
exports.onNode = onNode;
|
|
10
|
+
/**
|
|
11
|
+
* It is an example htmlnano module.
|
|
12
|
+
*
|
|
13
|
+
* A htmlnano module can be modify the attributes of every node (through a "onAttrs" named export),
|
|
14
|
+
* modify the content of every node (through an optional "onContent" named export), modify the node
|
|
15
|
+
* itself (through an optional "onNode" named export), or modify the entire tree (through an optional
|
|
16
|
+
* default export).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Modify attributes of node. Optional.
|
|
21
|
+
*
|
|
22
|
+
* @param {object} options - Options that were passed to htmlnano
|
|
23
|
+
* @param moduleOptions — Module options. For most modules this is just "true" (indication that the module was enabled)
|
|
24
|
+
* @return {Function} - Return a function that takes attribute object and the node (for the context), and returns the modified attribute object
|
|
25
|
+
*/
|
|
26
|
+
function onAttrs(options, moduleOptions) {
|
|
27
|
+
return (attrs, node) => {
|
|
28
|
+
// You can modify "attrs" based on "node"
|
|
29
|
+
const newAttrs = {
|
|
30
|
+
...attrs
|
|
31
|
+
};
|
|
32
|
+
return newAttrs; // ... then return the modified attrs
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Modify content of node. Optional.
|
|
38
|
+
*
|
|
39
|
+
* @param {object} options - Options that were passed to htmlnano
|
|
40
|
+
* @param moduleOptions — Module options. For most modules this is just "true" (indication that the module was enabled)
|
|
41
|
+
* @return {Function} - Return a function that takes contents (an array of node and string) and the node (for the context), and returns the modified content array.
|
|
42
|
+
*/
|
|
43
|
+
function onContent(options, moduleOptions) {
|
|
44
|
+
return (content, node) => {
|
|
45
|
+
// Same goes the "content"
|
|
46
|
+
|
|
47
|
+
return content; // ... return modified content here
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* It is possible to modify entire ndde as well. Optional.
|
|
53
|
+
* @param {object} options - Options that were passed to htmlnano
|
|
54
|
+
* @param moduleOptions — Module options. For most modules this is just "true" (indication that the module was enabled)
|
|
55
|
+
* @return {Function} - Return a function that takes the node, and returns the new, modified node.
|
|
56
|
+
*/
|
|
57
|
+
function onNode(options, moduleOptions) {
|
|
58
|
+
return node => {
|
|
59
|
+
return node; // ... return new node here
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Modify the entire tree. Optional.
|
|
65
|
+
*
|
|
66
|
+
* @param {object} tree - PostHTML tree (https://github.com/posthtml/posthtml/blob/master/README.md)
|
|
67
|
+
* @param {object} options - Options that were passed to htmlnano
|
|
68
|
+
* @param moduleOptions — Module options. For most modules this is just "true" (indication that the module was enabled)
|
|
69
|
+
* @return {object | Proimse} - Return the modified tree.
|
|
70
|
+
*/
|
|
71
|
+
function example(tree, options, moduleOptions) {
|
|
72
|
+
// Module filename (example.es6), exported default function name (example),
|
|
73
|
+
// and test filename (example.js) must be the same.
|
|
74
|
+
|
|
75
|
+
// You can traverse the tree...
|
|
76
|
+
tree.walk(node => {
|
|
77
|
+
// ...and make some minification
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// At the end you must return the tree
|
|
81
|
+
return tree;
|
|
82
|
+
|
|
83
|
+
// Or a promise with the tree
|
|
84
|
+
return somePromise.then(() => tree);
|
|
85
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* It is an example htmlnano module.
|
|
3
|
+
*
|
|
4
|
+
* A htmlnano module can be modify the attributes of every node (through a "onAttrs" named export),
|
|
5
|
+
* modify the content of every node (through an optional "onContent" named export), modify the node
|
|
6
|
+
* itself (through an optional "onNode" named export), or modify the entire tree (through an optional
|
|
7
|
+
* default export).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Modify attributes of node. Optional.
|
|
12
|
+
*
|
|
13
|
+
* @param {object} options - Options that were passed to htmlnano
|
|
14
|
+
* @param moduleOptions — Module options. For most modules this is just "true" (indication that the module was enabled)
|
|
15
|
+
* @return {Function} - Return a function that takes attribute object and the node (for the context), and returns the modified attribute object
|
|
16
|
+
*/
|
|
17
|
+
export function onAttrs(options, moduleOptions) {
|
|
18
|
+
return (attrs, node) => {
|
|
19
|
+
// You can modify "attrs" based on "node"
|
|
20
|
+
const newAttrs = { ...attrs };
|
|
21
|
+
|
|
22
|
+
return newAttrs; // ... then return the modified attrs
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Modify content of node. Optional.
|
|
28
|
+
*
|
|
29
|
+
* @param {object} options - Options that were passed to htmlnano
|
|
30
|
+
* @param moduleOptions — Module options. For most modules this is just "true" (indication that the module was enabled)
|
|
31
|
+
* @return {Function} - Return a function that takes contents (an array of node and string) and the node (for the context), and returns the modified content array.
|
|
32
|
+
*/
|
|
33
|
+
export function onContent(options, moduleOptions) {
|
|
34
|
+
return (content, node) => {
|
|
35
|
+
// Same goes the "content"
|
|
36
|
+
|
|
37
|
+
return content; // ... return modified content here
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* It is possible to modify entire ndde as well. Optional.
|
|
43
|
+
* @param {object} options - Options that were passed to htmlnano
|
|
44
|
+
* @param moduleOptions — Module options. For most modules this is just "true" (indication that the module was enabled)
|
|
45
|
+
* @return {Function} - Return a function that takes the node, and returns the new, modified node.
|
|
46
|
+
*/
|
|
47
|
+
export function onNode(options, moduleOptions) {
|
|
48
|
+
return (node) => {
|
|
49
|
+
return node; // ... return new node here
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Modify the entire tree. Optional.
|
|
55
|
+
*
|
|
56
|
+
* @param {object} tree - PostHTML tree (https://github.com/posthtml/posthtml/blob/master/README.md)
|
|
57
|
+
* @param {object} options - Options that were passed to htmlnano
|
|
58
|
+
* @param moduleOptions — Module options. For most modules this is just "true" (indication that the module was enabled)
|
|
59
|
+
* @return {object | Proimse} - Return the modified tree.
|
|
60
|
+
*/
|
|
61
|
+
export default function example(tree, options, moduleOptions) {
|
|
62
|
+
// Module filename (example.es6), exported default function name (example),
|
|
63
|
+
// and test filename (example.js) must be the same.
|
|
64
|
+
|
|
65
|
+
// You can traverse the tree...
|
|
66
|
+
tree.walk(node => {
|
|
67
|
+
// ...and make some minification
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// At the end you must return the tree
|
|
71
|
+
return tree;
|
|
72
|
+
|
|
73
|
+
// Or a promise with the tree
|
|
74
|
+
return somePromise.then(() => tree);
|
|
75
|
+
}
|
|
@@ -12,7 +12,9 @@ function mergeScripts(tree) {
|
|
|
12
12
|
tag: 'script'
|
|
13
13
|
}, node => {
|
|
14
14
|
const nodeAttrs = node.attrs || {};
|
|
15
|
-
if (
|
|
15
|
+
if ('src' in nodeAttrs
|
|
16
|
+
// Skip SRI, reasons are documented in "minifyJs" module
|
|
17
|
+
|| 'integrity' in nodeAttrs) {
|
|
16
18
|
scriptSrcIndex++;
|
|
17
19
|
return node;
|
|
18
20
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/* Merge multiple <script> into one */
|
|
2
|
+
export default function mergeScripts (tree) {
|
|
3
|
+
let scriptNodesIndex = {};
|
|
4
|
+
let scriptSrcIndex = 1;
|
|
5
|
+
|
|
6
|
+
tree.match({ tag: 'script' }, node => {
|
|
7
|
+
const nodeAttrs = node.attrs || {};
|
|
8
|
+
if (
|
|
9
|
+
'src' in nodeAttrs
|
|
10
|
+
// Skip SRI, reasons are documented in "minifyJs" module
|
|
11
|
+
|| 'integrity' in nodeAttrs
|
|
12
|
+
) {
|
|
13
|
+
scriptSrcIndex++;
|
|
14
|
+
return node;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const scriptType = nodeAttrs.type || 'text/javascript';
|
|
18
|
+
if (scriptType !== 'text/javascript' && scriptType !== 'application/javascript') {
|
|
19
|
+
return node;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const scriptKey = JSON.stringify({
|
|
23
|
+
id: nodeAttrs.id,
|
|
24
|
+
class: nodeAttrs.class,
|
|
25
|
+
type: scriptType,
|
|
26
|
+
defer: nodeAttrs.defer !== undefined,
|
|
27
|
+
async: nodeAttrs.async !== undefined,
|
|
28
|
+
index: scriptSrcIndex,
|
|
29
|
+
});
|
|
30
|
+
if (!scriptNodesIndex[scriptKey]) {
|
|
31
|
+
scriptNodesIndex[scriptKey] = [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
scriptNodesIndex[scriptKey].push(node);
|
|
35
|
+
return node;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
for (const scriptNodes of Object.values(scriptNodesIndex)) {
|
|
39
|
+
let lastScriptNode = scriptNodes.pop();
|
|
40
|
+
scriptNodes.reverse().forEach(scriptNode => {
|
|
41
|
+
let scriptContent = (scriptNode.content || []).join(' ');
|
|
42
|
+
scriptContent = scriptContent.trim();
|
|
43
|
+
if (scriptContent.slice(-1) !== ';') {
|
|
44
|
+
scriptContent += ';';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
lastScriptNode.content = lastScriptNode.content || [];
|
|
48
|
+
lastScriptNode.content.unshift(scriptContent);
|
|
49
|
+
|
|
50
|
+
scriptNode.tag = false;
|
|
51
|
+
scriptNode.content = [];
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return tree;
|
|
56
|
+
}
|
|
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = mergeStyles;
|
|
7
|
-
var _helpers = require("../helpers");
|
|
7
|
+
var _helpers = require("../helpers.cjs");
|
|
8
8
|
/* Merge multiple <style> into one */
|
|
9
9
|
function mergeStyles(tree) {
|
|
10
10
|
const styleNodes = {};
|
|
@@ -14,7 +14,9 @@ function mergeStyles(tree) {
|
|
|
14
14
|
const nodeAttrs = node.attrs || {};
|
|
15
15
|
// Skip <style scoped></style>
|
|
16
16
|
// https://developer.mozilla.org/en/docs/Web/HTML/Element/style
|
|
17
|
-
|
|
17
|
+
//
|
|
18
|
+
// Also skip SRI, reasons are documented in "minifyJs" module
|
|
19
|
+
if ('scoped' in nodeAttrs || 'integrity' in nodeAttrs) {
|
|
18
20
|
return node;
|
|
19
21
|
}
|
|
20
22
|
if ((0, _helpers.isAmpBoilerplate)(node)) {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { isAmpBoilerplate } from '../helpers.mjs';
|
|
2
|
+
|
|
3
|
+
/* Merge multiple <style> into one */
|
|
4
|
+
export default function mergeStyles(tree) {
|
|
5
|
+
const styleNodes = {};
|
|
6
|
+
|
|
7
|
+
tree.match({tag: 'style'}, node => {
|
|
8
|
+
const nodeAttrs = node.attrs || {};
|
|
9
|
+
// Skip <style scoped></style>
|
|
10
|
+
// https://developer.mozilla.org/en/docs/Web/HTML/Element/style
|
|
11
|
+
//
|
|
12
|
+
// Also skip SRI, reasons are documented in "minifyJs" module
|
|
13
|
+
if ('scoped' in nodeAttrs || 'integrity' in nodeAttrs) {
|
|
14
|
+
return node;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (isAmpBoilerplate(node)) {
|
|
18
|
+
return node;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const styleType = nodeAttrs.type || 'text/css';
|
|
22
|
+
const styleMedia = nodeAttrs.media || 'all';
|
|
23
|
+
const styleKey = styleType + '_' + styleMedia;
|
|
24
|
+
if (styleNodes[styleKey]) {
|
|
25
|
+
const styleContent = (node.content || []).join(' ');
|
|
26
|
+
styleNodes[styleKey].content.push(' ' + styleContent);
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
node.content = node.content || [];
|
|
31
|
+
styleNodes[styleKey] = node;
|
|
32
|
+
return node;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return tree;
|
|
36
|
+
}
|
|
@@ -4,8 +4,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = minifyConditionalComments;
|
|
7
|
-
var _htmlnano = _interopRequireDefault(require("../htmlnano"));
|
|
8
|
-
var _helpers = require("../helpers");
|
|
7
|
+
var _htmlnano = _interopRequireDefault(require("../htmlnano.cjs"));
|
|
8
|
+
var _helpers = require("../helpers.cjs");
|
|
9
9
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
10
10
|
// Spec: https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/ms537512(v=vs.85)
|
|
11
11
|
const CONDITIONAL_COMMENT_REGEXP = /(<!--\[if\s+?[^<>[\]]+?]>)([\s\S]+?)(<!\[endif\]-->)/gm;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import htmlnano from '../htmlnano.mjs';
|
|
2
|
+
import { isConditionalComment } from '../helpers.mjs';
|
|
3
|
+
|
|
4
|
+
// Spec: https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/ms537512(v=vs.85)
|
|
5
|
+
const CONDITIONAL_COMMENT_REGEXP = /(<!--\[if\s+?[^<>[\]]+?]>)([\s\S]+?)(<!\[endif\]-->)/gm;
|
|
6
|
+
|
|
7
|
+
/** Minify content inside conditional comments */
|
|
8
|
+
export default async function minifyConditionalComments(tree, htmlnanoOptions) {
|
|
9
|
+
// forEach, tree.walk, tree.match just don't support Promise.
|
|
10
|
+
for (let i = 0, len = tree.length; i < len; i++) {
|
|
11
|
+
const node = tree[i];
|
|
12
|
+
|
|
13
|
+
if (typeof node === 'string' && isConditionalComment(node)) {
|
|
14
|
+
tree[i] = await minifycontentInsideConditionalComments(node, htmlnanoOptions);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (node.content && node.content.length) {
|
|
18
|
+
tree[i].content = await minifyConditionalComments(node.content, htmlnanoOptions);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return tree;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function minifycontentInsideConditionalComments(text, htmlnanoOptions) {
|
|
26
|
+
let match;
|
|
27
|
+
const matches = [];
|
|
28
|
+
|
|
29
|
+
// FIXME!
|
|
30
|
+
// String#matchAll is supported since Node.js 12
|
|
31
|
+
while ((match = CONDITIONAL_COMMENT_REGEXP.exec(text)) !== null) {
|
|
32
|
+
matches.push([match[1], match[2], match[3]]);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!matches.length) {
|
|
36
|
+
return Promise.resolve(text);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return Promise.all(matches.map(async match => {
|
|
40
|
+
const result = await htmlnano.process(match[1], htmlnanoOptions, {}, {});
|
|
41
|
+
let minified = result.html;
|
|
42
|
+
|
|
43
|
+
if (match[1].includes('<html') && minified.includes('</html>')) {
|
|
44
|
+
minified = minified.replace('</html>', '');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return match[0] + minified + match[2];
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
@@ -4,9 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = minifyCss;
|
|
7
|
-
var _helpers = require("../helpers");
|
|
8
|
-
const cssnano = (0, _helpers.optionalRequire)('cssnano');
|
|
9
|
-
const postcss = (0, _helpers.optionalRequire)('postcss');
|
|
7
|
+
var _helpers = require("../helpers.cjs");
|
|
10
8
|
const postcssOptions = {
|
|
11
9
|
// Prevent the following warning from being shown:
|
|
12
10
|
// > Without `from` option PostCSS could generate wrong source map and will not find Browserslist config.
|
|
@@ -15,22 +13,28 @@ const postcssOptions = {
|
|
|
15
13
|
};
|
|
16
14
|
|
|
17
15
|
/** Minify CSS with cssnano */
|
|
18
|
-
function minifyCss(tree, options, cssnanoOptions) {
|
|
16
|
+
async function minifyCss(tree, options, cssnanoOptions) {
|
|
17
|
+
const cssnano = await (0, _helpers.optionalImport)('cssnano');
|
|
18
|
+
const postcss = await (0, _helpers.optionalImport)('postcss');
|
|
19
19
|
if (!cssnano || !postcss) {
|
|
20
20
|
return tree;
|
|
21
21
|
}
|
|
22
22
|
let promises = [];
|
|
23
23
|
tree.walk(node => {
|
|
24
|
+
// Skip SRI, reasons are documented in "minifyJs" module
|
|
25
|
+
if (node.attrs && 'integrity' in node.attrs) {
|
|
26
|
+
return node;
|
|
27
|
+
}
|
|
24
28
|
if ((0, _helpers.isStyleNode)(node)) {
|
|
25
|
-
promises.push(processStyleNode(node, cssnanoOptions));
|
|
29
|
+
promises.push(processStyleNode(node, cssnanoOptions, cssnano, postcss));
|
|
26
30
|
} else if (node.attrs && node.attrs.style) {
|
|
27
|
-
promises.push(processStyleAttr(node, cssnanoOptions));
|
|
31
|
+
promises.push(processStyleAttr(node, cssnanoOptions, cssnano, postcss));
|
|
28
32
|
}
|
|
29
33
|
return node;
|
|
30
34
|
});
|
|
31
35
|
return Promise.all(promises).then(() => tree);
|
|
32
36
|
}
|
|
33
|
-
function processStyleNode(styleNode, cssnanoOptions) {
|
|
37
|
+
function processStyleNode(styleNode, cssnanoOptions, cssnano, postcss) {
|
|
34
38
|
let css = (0, _helpers.extractCssFromStyleNode)(styleNode);
|
|
35
39
|
|
|
36
40
|
// Improve performance by avoiding calling stripCdata again and again
|
|
@@ -47,7 +51,7 @@ function processStyleNode(styleNode, cssnanoOptions) {
|
|
|
47
51
|
return styleNode.content = [result.css];
|
|
48
52
|
});
|
|
49
53
|
}
|
|
50
|
-
function processStyleAttr(node, cssnanoOptions) {
|
|
54
|
+
function processStyleAttr(node, cssnanoOptions, cssnano, postcss) {
|
|
51
55
|
// CSS "color: red;" is invalid. Therefore it should be wrapped inside some selector:
|
|
52
56
|
// a{color: red;}
|
|
53
57
|
const wrapperStart = 'a{';
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { isStyleNode, extractCssFromStyleNode, optionalImport } from '../helpers.mjs';
|
|
2
|
+
|
|
3
|
+
const postcssOptions = {
|
|
4
|
+
// Prevent the following warning from being shown:
|
|
5
|
+
// > Without `from` option PostCSS could generate wrong source map and will not find Browserslist config.
|
|
6
|
+
// > Set it to CSS file path or to `undefined` to prevent this warning.
|
|
7
|
+
from: undefined,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/** Minify CSS with cssnano */
|
|
11
|
+
export default async function minifyCss(tree, options, cssnanoOptions) {
|
|
12
|
+
const cssnano = await optionalImport('cssnano');
|
|
13
|
+
const postcss = await optionalImport('postcss');
|
|
14
|
+
|
|
15
|
+
if (!cssnano || !postcss) {
|
|
16
|
+
return tree;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let promises = [];
|
|
20
|
+
tree.walk(node => {
|
|
21
|
+
// Skip SRI, reasons are documented in "minifyJs" module
|
|
22
|
+
if (node.attrs && 'integrity' in node.attrs) {
|
|
23
|
+
return node;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (isStyleNode(node)) {
|
|
27
|
+
promises.push(processStyleNode(node, cssnanoOptions, cssnano, postcss));
|
|
28
|
+
} else if (node.attrs && node.attrs.style) {
|
|
29
|
+
promises.push(processStyleAttr(node, cssnanoOptions, cssnano, postcss));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return node;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return Promise.all(promises).then(() => tree);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
function processStyleNode(styleNode, cssnanoOptions, cssnano, postcss) {
|
|
40
|
+
let css = extractCssFromStyleNode(styleNode);
|
|
41
|
+
|
|
42
|
+
// Improve performance by avoiding calling stripCdata again and again
|
|
43
|
+
let isCdataWrapped = false;
|
|
44
|
+
if (css.includes('CDATA')) {
|
|
45
|
+
const strippedCss = stripCdata(css);
|
|
46
|
+
isCdataWrapped = css !== strippedCss;
|
|
47
|
+
css = strippedCss;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return postcss([cssnano(cssnanoOptions)])
|
|
51
|
+
.process(css, postcssOptions)
|
|
52
|
+
.then(result => {
|
|
53
|
+
if (isCdataWrapped) {
|
|
54
|
+
return styleNode.content = ['<![CDATA[' + result + ']]>'];
|
|
55
|
+
}
|
|
56
|
+
return styleNode.content = [result.css];
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
function processStyleAttr(node, cssnanoOptions, cssnano, postcss) {
|
|
62
|
+
// CSS "color: red;" is invalid. Therefore it should be wrapped inside some selector:
|
|
63
|
+
// a{color: red;}
|
|
64
|
+
const wrapperStart = 'a{';
|
|
65
|
+
const wrapperEnd = '}';
|
|
66
|
+
const wrappedStyle = wrapperStart + (node.attrs.style || '') + wrapperEnd;
|
|
67
|
+
|
|
68
|
+
return postcss([cssnano(cssnanoOptions)])
|
|
69
|
+
.process(wrappedStyle, postcssOptions)
|
|
70
|
+
.then(result => {
|
|
71
|
+
const minifiedCss = result.css;
|
|
72
|
+
// Remove wrapperStart at the start and wrapperEnd at the end of minifiedCss
|
|
73
|
+
node.attrs.style = minifiedCss.substring(
|
|
74
|
+
wrapperStart.length,
|
|
75
|
+
minifiedCss.length - wrapperEnd.length
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function stripCdata(css) {
|
|
81
|
+
const leftStrippedCss = css.replace('<![CDATA[', '');
|
|
82
|
+
if (leftStrippedCss === css) {
|
|
83
|
+
return css;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const strippedCss = leftStrippedCss.replace(']]>', '');
|
|
87
|
+
return leftStrippedCss === strippedCss ? css : strippedCss;
|
|
88
|
+
}
|
|
@@ -4,24 +4,39 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = minifyJs;
|
|
7
|
-
var _helpers = require("../helpers");
|
|
8
|
-
var _removeRedundantAttributes = require("./removeRedundantAttributes");
|
|
9
|
-
const terser = (0, _helpers.optionalRequire)('terser');
|
|
10
|
-
|
|
7
|
+
var _helpers = require("../helpers.cjs");
|
|
8
|
+
var _removeRedundantAttributes = require("./removeRedundantAttributes.cjs");
|
|
11
9
|
/** Minify JS with Terser */
|
|
12
|
-
function minifyJs(tree, options, terserOptions) {
|
|
10
|
+
async function minifyJs(tree, options, terserOptions) {
|
|
11
|
+
const terser = await (0, _helpers.optionalImport)('terser');
|
|
13
12
|
if (!terser) return tree;
|
|
14
13
|
let promises = [];
|
|
15
14
|
tree.walk(node => {
|
|
15
|
+
const nodeAttrs = node.attrs || {};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Skip SRI
|
|
19
|
+
*
|
|
20
|
+
* If the input <script /> has an SRI attribute, it means that the original <script /> could be trusted,
|
|
21
|
+
* and should not be altered anymore.
|
|
22
|
+
*
|
|
23
|
+
* htmlnano is exactly an MITM that SRI is designed to protect from. If htmlnano or its dependencies get
|
|
24
|
+
* compromised and introduces malicious code, then it is up to the original SRI to protect the end user.
|
|
25
|
+
*
|
|
26
|
+
* So htmlnano will simply skip <script /> that has SRI.
|
|
27
|
+
* If developers do trust htmlnano, they should generate SRI after htmlnano modify the <script />.
|
|
28
|
+
*/
|
|
29
|
+
if ('integrity' in nodeAttrs) {
|
|
30
|
+
return node;
|
|
31
|
+
}
|
|
16
32
|
if (node.tag && node.tag === 'script') {
|
|
17
|
-
const nodeAttrs = node.attrs || {};
|
|
18
33
|
const mimeType = nodeAttrs.type || 'text/javascript';
|
|
19
34
|
if (_removeRedundantAttributes.redundantScriptTypes.has(mimeType) || mimeType === 'module') {
|
|
20
|
-
promises.push(processScriptNode(node, terserOptions));
|
|
35
|
+
promises.push(processScriptNode(node, terserOptions, terser));
|
|
21
36
|
}
|
|
22
37
|
}
|
|
23
38
|
if (node.attrs) {
|
|
24
|
-
promises = promises.concat(processNodeWithOnAttrs(node, terserOptions));
|
|
39
|
+
promises = promises.concat(processNodeWithOnAttrs(node, terserOptions, terser));
|
|
25
40
|
}
|
|
26
41
|
return node;
|
|
27
42
|
});
|
|
@@ -35,7 +50,7 @@ function stripCdata(js) {
|
|
|
35
50
|
const strippedJs = leftStrippedJs.replace(/\/\/\s*\]\]>/, '').replace(/\/\*\s*\]\]>\s*\*\//, '');
|
|
36
51
|
return leftStrippedJs === strippedJs ? js : strippedJs;
|
|
37
52
|
}
|
|
38
|
-
function processScriptNode(scriptNode, terserOptions) {
|
|
53
|
+
function processScriptNode(scriptNode, terserOptions, terser) {
|
|
39
54
|
let js = (scriptNode.content || []).join('').trim();
|
|
40
55
|
if (!js) {
|
|
41
56
|
return scriptNode;
|
|
@@ -62,7 +77,7 @@ function processScriptNode(scriptNode, terserOptions) {
|
|
|
62
77
|
scriptNode.content = [content];
|
|
63
78
|
});
|
|
64
79
|
}
|
|
65
|
-
function processNodeWithOnAttrs(node, terserOptions) {
|
|
80
|
+
function processNodeWithOnAttrs(node, terserOptions, terser) {
|
|
66
81
|
const jsWrapperStart = 'a=function(){';
|
|
67
82
|
const jsWrapperEnd = '};a();';
|
|
68
83
|
const promises = [];
|