htmlnano 2.1.5 → 3.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/README.md +24 -17
- package/dist/_modules/collapseAttributeWhitespace.d.mts +42 -5
- package/dist/_modules/collapseAttributeWhitespace.d.ts +42 -5
- package/dist/_modules/collapseAttributeWhitespace.js +69 -16
- package/dist/_modules/collapseAttributeWhitespace.mjs +67 -17
- package/dist/_modules/collapseBooleanAttributes.d.mts +37 -3
- package/dist/_modules/collapseBooleanAttributes.d.ts +37 -3
- package/dist/_modules/collapseBooleanAttributes.js +18 -11
- package/dist/_modules/collapseBooleanAttributes.mjs +18 -11
- package/dist/_modules/collapseWhitespace.d.mts +37 -3
- package/dist/_modules/collapseWhitespace.d.ts +37 -3
- package/dist/_modules/collapseWhitespace.js +25 -2
- package/dist/_modules/collapseWhitespace.mjs +25 -2
- package/dist/_modules/custom.d.mts +37 -3
- package/dist/_modules/custom.d.ts +37 -3
- package/dist/_modules/deduplicateAttributeValues.d.mts +37 -3
- package/dist/_modules/deduplicateAttributeValues.d.ts +37 -3
- package/dist/_modules/deduplicateAttributeValues.js +20 -5
- package/dist/_modules/deduplicateAttributeValues.mjs +21 -6
- package/dist/_modules/example.d.mts +37 -3
- package/dist/_modules/example.d.ts +37 -3
- package/dist/_modules/mergeScripts.d.mts +37 -3
- package/dist/_modules/mergeScripts.d.ts +37 -3
- package/dist/_modules/mergeScripts.js +99 -24
- package/dist/_modules/mergeScripts.mjs +99 -24
- package/dist/_modules/mergeStyles.d.mts +37 -3
- package/dist/_modules/mergeStyles.d.ts +37 -3
- package/dist/_modules/mergeStyles.js +57 -6
- package/dist/_modules/mergeStyles.mjs +57 -6
- package/dist/_modules/minifyAttributes.d.mts +95 -0
- package/dist/_modules/minifyAttributes.d.ts +95 -0
- package/dist/_modules/minifyAttributes.js +159 -0
- package/dist/_modules/minifyAttributes.mjs +157 -0
- package/dist/_modules/minifyConditionalComments.d.mts +37 -3
- package/dist/_modules/minifyConditionalComments.d.ts +37 -3
- package/dist/_modules/minifyConditionalComments.js +37 -19
- package/dist/_modules/minifyConditionalComments.mjs +37 -19
- package/dist/_modules/minifyCss.d.mts +37 -3
- package/dist/_modules/minifyCss.d.ts +37 -3
- package/dist/_modules/minifyCss.js +13 -27
- package/dist/_modules/minifyCss.mjs +14 -28
- package/dist/_modules/minifyHtmlTemplate.d.mts +91 -0
- package/dist/_modules/minifyHtmlTemplate.d.ts +91 -0
- package/dist/_modules/minifyHtmlTemplate.js +231 -0
- package/dist/_modules/minifyHtmlTemplate.mjs +228 -0
- package/dist/_modules/minifyJs.d.mts +37 -3
- package/dist/_modules/minifyJs.d.ts +37 -3
- package/dist/_modules/minifyJs.js +94 -5
- package/dist/_modules/minifyJs.mjs +95 -6
- package/dist/_modules/minifyJson.d.mts +37 -3
- package/dist/_modules/minifyJson.d.ts +37 -3
- package/dist/_modules/minifyJson.js +9 -12
- package/dist/_modules/minifyJson.mjs +9 -12
- package/dist/_modules/minifySvg.d.mts +37 -3
- package/dist/_modules/minifySvg.d.ts +37 -3
- package/dist/_modules/minifySvg.js +36 -5
- package/dist/_modules/minifySvg.mjs +36 -5
- package/dist/_modules/minifyUrls.d.mts +38 -4
- package/dist/_modules/minifyUrls.d.ts +38 -4
- package/dist/_modules/minifyUrls.js +53 -28
- package/dist/_modules/minifyUrls.mjs +53 -28
- package/dist/_modules/normalizeAttributeValues.d.mts +37 -3
- package/dist/_modules/normalizeAttributeValues.d.ts +37 -3
- package/dist/_modules/normalizeAttributeValues.js +10 -8
- package/dist/_modules/normalizeAttributeValues.mjs +10 -8
- package/dist/_modules/removeAttributeQuotes.d.mts +41 -4
- package/dist/_modules/removeAttributeQuotes.d.ts +41 -4
- package/dist/_modules/removeAttributeQuotes.js +9 -5
- package/dist/_modules/removeAttributeQuotes.mjs +9 -5
- package/dist/_modules/removeComments.d.mts +38 -4
- package/dist/_modules/removeComments.d.ts +38 -4
- package/dist/_modules/removeComments.js +44 -12
- package/dist/_modules/removeComments.mjs +44 -12
- package/dist/_modules/removeEmptyAttributes.d.mts +37 -3
- package/dist/_modules/removeEmptyAttributes.d.ts +37 -3
- package/dist/_modules/removeEmptyAttributes.js +37 -16
- package/dist/_modules/removeEmptyAttributes.mjs +37 -16
- package/dist/_modules/removeEmptyElements.d.mts +95 -0
- package/dist/_modules/removeEmptyElements.d.ts +95 -0
- package/dist/_modules/removeEmptyElements.js +90 -0
- package/dist/_modules/removeEmptyElements.mjs +88 -0
- package/dist/_modules/removeOptionalTags.d.mts +37 -3
- package/dist/_modules/removeOptionalTags.d.ts +37 -3
- package/dist/_modules/removeOptionalTags.js +39 -28
- package/dist/_modules/removeOptionalTags.mjs +39 -28
- package/dist/_modules/removeRedundantAttributes.d.mts +37 -3
- package/dist/_modules/removeRedundantAttributes.d.ts +37 -3
- package/dist/_modules/removeRedundantAttributes.js +43 -28
- package/dist/_modules/removeRedundantAttributes.mjs +43 -28
- package/dist/_modules/removeUnusedCss.d.mts +38 -3
- package/dist/_modules/removeUnusedCss.d.ts +38 -3
- package/dist/_modules/removeUnusedCss.js +38 -13
- package/dist/_modules/removeUnusedCss.mjs +39 -14
- package/dist/_modules/sortAttributes.d.mts +37 -3
- package/dist/_modules/sortAttributes.d.ts +37 -3
- package/dist/_modules/sortAttributes.js +23 -5
- package/dist/_modules/sortAttributes.mjs +23 -5
- package/dist/_modules/sortAttributesWithLists.d.mts +37 -3
- package/dist/_modules/sortAttributesWithLists.d.ts +37 -3
- package/dist/_modules/sortAttributesWithLists.js +30 -8
- package/dist/_modules/sortAttributesWithLists.mjs +31 -9
- package/dist/bin.js +34 -0
- package/dist/helpers.d.ts +8 -1
- package/dist/helpers.js +48 -0
- package/dist/helpers.mjs +45 -1
- package/dist/index.d.ts +43 -8
- package/dist/index.js +20 -5
- package/dist/index.mjs +20 -6
- package/dist/presets/ampSafe.d.ts +38 -4
- package/dist/presets/max.d.ts +38 -4
- package/dist/presets/max.js +4 -0
- package/dist/presets/max.mjs +4 -0
- package/dist/presets/safe.d.ts +38 -4
- package/dist/presets/safe.js +6 -0
- package/dist/presets/safe.mjs +6 -0
- package/package.json +27 -16
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
+
|
|
3
|
+
var helpers_js = require('../helpers.js');
|
|
4
|
+
var collapseAttributeWhitespace_js = require('./collapseAttributeWhitespace.js');
|
|
5
|
+
|
|
6
|
+
const asciiWhitespace = new Set([
|
|
7
|
+
'\t',
|
|
8
|
+
'\n',
|
|
9
|
+
'\f',
|
|
10
|
+
'\r',
|
|
11
|
+
' '
|
|
12
|
+
]);
|
|
13
|
+
const defaultOptions = {
|
|
14
|
+
metaContent: true,
|
|
15
|
+
redundantWhitespaces: 'safe'
|
|
16
|
+
};
|
|
17
|
+
function isAsciiWhitespace(char) {
|
|
18
|
+
return asciiWhitespace.has(char);
|
|
19
|
+
}
|
|
20
|
+
function isAsciiDigit(char) {
|
|
21
|
+
return char >= '0' && char <= '9';
|
|
22
|
+
}
|
|
23
|
+
function skipAsciiWhitespace(input, start) {
|
|
24
|
+
let pos = start;
|
|
25
|
+
while(pos < input.length && isAsciiWhitespace(input[pos])){
|
|
26
|
+
pos += 1;
|
|
27
|
+
}
|
|
28
|
+
return pos;
|
|
29
|
+
}
|
|
30
|
+
function minifyMetaRefreshValue(value) {
|
|
31
|
+
const input = value;
|
|
32
|
+
let pos = skipAsciiWhitespace(input, 0);
|
|
33
|
+
const timeStart = pos;
|
|
34
|
+
while(pos < input.length && isAsciiDigit(input[pos])){
|
|
35
|
+
pos += 1;
|
|
36
|
+
}
|
|
37
|
+
if (pos === timeStart) return null;
|
|
38
|
+
const time = input.slice(timeStart, pos);
|
|
39
|
+
while(pos < input.length){
|
|
40
|
+
const ch = input[pos];
|
|
41
|
+
if (isAsciiDigit(ch) || ch === '.') {
|
|
42
|
+
pos += 1;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
pos = skipAsciiWhitespace(input, pos);
|
|
48
|
+
if (pos >= input.length) return time;
|
|
49
|
+
const separator = input[pos];
|
|
50
|
+
if (separator !== ';' && separator !== ',') return null;
|
|
51
|
+
pos += 1;
|
|
52
|
+
pos = skipAsciiWhitespace(input, pos);
|
|
53
|
+
if (pos >= input.length) return time;
|
|
54
|
+
let hasUrlPrefix = false;
|
|
55
|
+
if (input[pos] === 'u' || input[pos] === 'U') {
|
|
56
|
+
if (pos + 2 < input.length) {
|
|
57
|
+
const maybeUrl = input.slice(pos, pos + 3);
|
|
58
|
+
if (maybeUrl.toLowerCase() === 'url') {
|
|
59
|
+
let prefixPos = skipAsciiWhitespace(input, pos + 3);
|
|
60
|
+
if (prefixPos < input.length && input[prefixPos] === '=') {
|
|
61
|
+
prefixPos = skipAsciiWhitespace(input, prefixPos + 1);
|
|
62
|
+
hasUrlPrefix = true;
|
|
63
|
+
pos = prefixPos;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (pos >= input.length) return time;
|
|
69
|
+
const firstUrlChar = input[pos];
|
|
70
|
+
if (firstUrlChar === '"' || firstUrlChar === '\'') {
|
|
71
|
+
if (!hasUrlPrefix) return null;
|
|
72
|
+
const quote = firstUrlChar;
|
|
73
|
+
const urlStart = pos + 1;
|
|
74
|
+
const quoteIndex = input.indexOf(quote, urlStart);
|
|
75
|
+
const url = quoteIndex === -1 ? input.slice(urlStart) : input.slice(urlStart, quoteIndex);
|
|
76
|
+
if (!url) return time;
|
|
77
|
+
const closingQuote = quoteIndex === -1 ? '' : quote;
|
|
78
|
+
return `${time}${separator} URL=${quote}${url}${closingQuote}`;
|
|
79
|
+
}
|
|
80
|
+
const url = input.slice(pos).trim();
|
|
81
|
+
if (!url) return time;
|
|
82
|
+
return `${time}${separator} ${url}`;
|
|
83
|
+
}
|
|
84
|
+
function isMetaRefresh(attrs, tagName) {
|
|
85
|
+
if (!tagName || tagName.toLowerCase() !== 'meta') return false;
|
|
86
|
+
const httpEquiv = attrs['http-equiv'];
|
|
87
|
+
if (typeof httpEquiv !== 'string') return false;
|
|
88
|
+
return httpEquiv.trim().toLowerCase() === 'refresh';
|
|
89
|
+
}
|
|
90
|
+
function normalizeOptions(moduleOptions) {
|
|
91
|
+
if (moduleOptions && typeof moduleOptions === 'object') {
|
|
92
|
+
let redundantWhitespaces = defaultOptions.redundantWhitespaces;
|
|
93
|
+
if (moduleOptions.redundantWhitespaces === 'aggressive') {
|
|
94
|
+
redundantWhitespaces = 'agressive';
|
|
95
|
+
} else if (moduleOptions.redundantWhitespaces === 'safe' || moduleOptions.redundantWhitespaces === 'agressive' || moduleOptions.redundantWhitespaces === false) {
|
|
96
|
+
redundantWhitespaces = moduleOptions.redundantWhitespaces;
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
metaContent: moduleOptions.metaContent !== false,
|
|
100
|
+
redundantWhitespaces
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return defaultOptions;
|
|
104
|
+
}
|
|
105
|
+
function collapseWhitespace(value) {
|
|
106
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
107
|
+
}
|
|
108
|
+
function minifyAttributeWhitespace(mode, attrName, attrValue, tagName) {
|
|
109
|
+
if (!mode) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const attrNameLower = attrName.toLowerCase();
|
|
113
|
+
if (collapseAttributeWhitespace_js.isListAttribute(attrNameLower, tagName)) {
|
|
114
|
+
const collapsed = collapseWhitespace(attrValue);
|
|
115
|
+
return collapsed === attrValue ? null : collapsed;
|
|
116
|
+
}
|
|
117
|
+
if (helpers_js.isEventHandler(attrName)) {
|
|
118
|
+
const trimmed = attrValue.trim();
|
|
119
|
+
return trimmed === attrValue ? null : trimmed;
|
|
120
|
+
}
|
|
121
|
+
if (collapseAttributeWhitespace_js.isSingleValueAttribute(attrNameLower, tagName)) {
|
|
122
|
+
const trimmed = attrValue.trim();
|
|
123
|
+
return trimmed === attrValue ? null : trimmed;
|
|
124
|
+
}
|
|
125
|
+
if (mode === 'agressive') {
|
|
126
|
+
const trimmed = attrValue.trim();
|
|
127
|
+
return trimmed === attrValue ? null : trimmed;
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const mod = {
|
|
132
|
+
onAttrs (_options, moduleOptions) {
|
|
133
|
+
const normalizedOptions = normalizeOptions(moduleOptions);
|
|
134
|
+
return (attrs, node)=>{
|
|
135
|
+
if (normalizedOptions.metaContent && isMetaRefresh(attrs, node.tag)) {
|
|
136
|
+
const content = attrs.content;
|
|
137
|
+
if (typeof content === 'string') {
|
|
138
|
+
const minified = minifyMetaRefreshValue(content);
|
|
139
|
+
if (minified !== null && minified !== content) {
|
|
140
|
+
attrs.content = minified;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (normalizedOptions.redundantWhitespaces) {
|
|
145
|
+
const tagName = node.tag ? node.tag.toLowerCase() : undefined;
|
|
146
|
+
Object.entries(attrs).forEach(([attrName, attrValue])=>{
|
|
147
|
+
if (typeof attrValue !== 'string') return;
|
|
148
|
+
const minified = minifyAttributeWhitespace(normalizedOptions.redundantWhitespaces, attrName, attrValue, tagName);
|
|
149
|
+
if (minified !== null) {
|
|
150
|
+
attrs[attrName] = minified;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return attrs;
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
exports.default = mod;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { isEventHandler } from '../helpers.mjs';
|
|
2
|
+
import { isListAttribute, isSingleValueAttribute } from './collapseAttributeWhitespace.mjs';
|
|
3
|
+
|
|
4
|
+
const asciiWhitespace = new Set([
|
|
5
|
+
'\t',
|
|
6
|
+
'\n',
|
|
7
|
+
'\f',
|
|
8
|
+
'\r',
|
|
9
|
+
' '
|
|
10
|
+
]);
|
|
11
|
+
const defaultOptions = {
|
|
12
|
+
metaContent: true,
|
|
13
|
+
redundantWhitespaces: 'safe'
|
|
14
|
+
};
|
|
15
|
+
function isAsciiWhitespace(char) {
|
|
16
|
+
return asciiWhitespace.has(char);
|
|
17
|
+
}
|
|
18
|
+
function isAsciiDigit(char) {
|
|
19
|
+
return char >= '0' && char <= '9';
|
|
20
|
+
}
|
|
21
|
+
function skipAsciiWhitespace(input, start) {
|
|
22
|
+
let pos = start;
|
|
23
|
+
while(pos < input.length && isAsciiWhitespace(input[pos])){
|
|
24
|
+
pos += 1;
|
|
25
|
+
}
|
|
26
|
+
return pos;
|
|
27
|
+
}
|
|
28
|
+
function minifyMetaRefreshValue(value) {
|
|
29
|
+
const input = value;
|
|
30
|
+
let pos = skipAsciiWhitespace(input, 0);
|
|
31
|
+
const timeStart = pos;
|
|
32
|
+
while(pos < input.length && isAsciiDigit(input[pos])){
|
|
33
|
+
pos += 1;
|
|
34
|
+
}
|
|
35
|
+
if (pos === timeStart) return null;
|
|
36
|
+
const time = input.slice(timeStart, pos);
|
|
37
|
+
while(pos < input.length){
|
|
38
|
+
const ch = input[pos];
|
|
39
|
+
if (isAsciiDigit(ch) || ch === '.') {
|
|
40
|
+
pos += 1;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
pos = skipAsciiWhitespace(input, pos);
|
|
46
|
+
if (pos >= input.length) return time;
|
|
47
|
+
const separator = input[pos];
|
|
48
|
+
if (separator !== ';' && separator !== ',') return null;
|
|
49
|
+
pos += 1;
|
|
50
|
+
pos = skipAsciiWhitespace(input, pos);
|
|
51
|
+
if (pos >= input.length) return time;
|
|
52
|
+
let hasUrlPrefix = false;
|
|
53
|
+
if (input[pos] === 'u' || input[pos] === 'U') {
|
|
54
|
+
if (pos + 2 < input.length) {
|
|
55
|
+
const maybeUrl = input.slice(pos, pos + 3);
|
|
56
|
+
if (maybeUrl.toLowerCase() === 'url') {
|
|
57
|
+
let prefixPos = skipAsciiWhitespace(input, pos + 3);
|
|
58
|
+
if (prefixPos < input.length && input[prefixPos] === '=') {
|
|
59
|
+
prefixPos = skipAsciiWhitespace(input, prefixPos + 1);
|
|
60
|
+
hasUrlPrefix = true;
|
|
61
|
+
pos = prefixPos;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (pos >= input.length) return time;
|
|
67
|
+
const firstUrlChar = input[pos];
|
|
68
|
+
if (firstUrlChar === '"' || firstUrlChar === '\'') {
|
|
69
|
+
if (!hasUrlPrefix) return null;
|
|
70
|
+
const quote = firstUrlChar;
|
|
71
|
+
const urlStart = pos + 1;
|
|
72
|
+
const quoteIndex = input.indexOf(quote, urlStart);
|
|
73
|
+
const url = quoteIndex === -1 ? input.slice(urlStart) : input.slice(urlStart, quoteIndex);
|
|
74
|
+
if (!url) return time;
|
|
75
|
+
const closingQuote = quoteIndex === -1 ? '' : quote;
|
|
76
|
+
return `${time}${separator} URL=${quote}${url}${closingQuote}`;
|
|
77
|
+
}
|
|
78
|
+
const url = input.slice(pos).trim();
|
|
79
|
+
if (!url) return time;
|
|
80
|
+
return `${time}${separator} ${url}`;
|
|
81
|
+
}
|
|
82
|
+
function isMetaRefresh(attrs, tagName) {
|
|
83
|
+
if (!tagName || tagName.toLowerCase() !== 'meta') return false;
|
|
84
|
+
const httpEquiv = attrs['http-equiv'];
|
|
85
|
+
if (typeof httpEquiv !== 'string') return false;
|
|
86
|
+
return httpEquiv.trim().toLowerCase() === 'refresh';
|
|
87
|
+
}
|
|
88
|
+
function normalizeOptions(moduleOptions) {
|
|
89
|
+
if (moduleOptions && typeof moduleOptions === 'object') {
|
|
90
|
+
let redundantWhitespaces = defaultOptions.redundantWhitespaces;
|
|
91
|
+
if (moduleOptions.redundantWhitespaces === 'aggressive') {
|
|
92
|
+
redundantWhitespaces = 'agressive';
|
|
93
|
+
} else if (moduleOptions.redundantWhitespaces === 'safe' || moduleOptions.redundantWhitespaces === 'agressive' || moduleOptions.redundantWhitespaces === false) {
|
|
94
|
+
redundantWhitespaces = moduleOptions.redundantWhitespaces;
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
metaContent: moduleOptions.metaContent !== false,
|
|
98
|
+
redundantWhitespaces
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return defaultOptions;
|
|
102
|
+
}
|
|
103
|
+
function collapseWhitespace(value) {
|
|
104
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
105
|
+
}
|
|
106
|
+
function minifyAttributeWhitespace(mode, attrName, attrValue, tagName) {
|
|
107
|
+
if (!mode) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const attrNameLower = attrName.toLowerCase();
|
|
111
|
+
if (isListAttribute(attrNameLower, tagName)) {
|
|
112
|
+
const collapsed = collapseWhitespace(attrValue);
|
|
113
|
+
return collapsed === attrValue ? null : collapsed;
|
|
114
|
+
}
|
|
115
|
+
if (isEventHandler(attrName)) {
|
|
116
|
+
const trimmed = attrValue.trim();
|
|
117
|
+
return trimmed === attrValue ? null : trimmed;
|
|
118
|
+
}
|
|
119
|
+
if (isSingleValueAttribute(attrNameLower, tagName)) {
|
|
120
|
+
const trimmed = attrValue.trim();
|
|
121
|
+
return trimmed === attrValue ? null : trimmed;
|
|
122
|
+
}
|
|
123
|
+
if (mode === 'agressive') {
|
|
124
|
+
const trimmed = attrValue.trim();
|
|
125
|
+
return trimmed === attrValue ? null : trimmed;
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const mod = {
|
|
130
|
+
onAttrs (_options, moduleOptions) {
|
|
131
|
+
const normalizedOptions = normalizeOptions(moduleOptions);
|
|
132
|
+
return (attrs, node)=>{
|
|
133
|
+
if (normalizedOptions.metaContent && isMetaRefresh(attrs, node.tag)) {
|
|
134
|
+
const content = attrs.content;
|
|
135
|
+
if (typeof content === 'string') {
|
|
136
|
+
const minified = minifyMetaRefreshValue(content);
|
|
137
|
+
if (minified !== null && minified !== content) {
|
|
138
|
+
attrs.content = minified;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (normalizedOptions.redundantWhitespaces) {
|
|
143
|
+
const tagName = node.tag ? node.tag.toLowerCase() : undefined;
|
|
144
|
+
Object.entries(attrs).forEach(([attrName, attrValue])=>{
|
|
145
|
+
if (typeof attrValue !== 'string') return;
|
|
146
|
+
const minified = minifyAttributeWhitespace(normalizedOptions.redundantWhitespaces, attrName, attrValue, tagName);
|
|
147
|
+
if (minified !== null) {
|
|
148
|
+
attrs[attrName] = minified;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return attrs;
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export { mod as default };
|
|
@@ -2,18 +2,27 @@ import PostHTML from 'posthtml';
|
|
|
2
2
|
import { MinifyOptions } from 'terser';
|
|
3
3
|
import { Options } from 'cssnano';
|
|
4
4
|
import { Config } from 'svgo';
|
|
5
|
+
import { UserDefinedOptions } from 'purgecss';
|
|
5
6
|
|
|
6
7
|
type PostHTMLTreeLike = [PostHTML.Node] & PostHTML.NodeAPI & {
|
|
7
8
|
options?: {
|
|
8
9
|
quoteAllAttributes?: boolean | undefined;
|
|
10
|
+
quoteStyle?: 0 | 1 | 2 | undefined;
|
|
11
|
+
replaceQuote?: boolean | undefined;
|
|
9
12
|
} | undefined;
|
|
10
13
|
render(): string;
|
|
11
14
|
render(node: PostHTML.Node | PostHTMLTreeLike, renderOptions?: any): string;
|
|
12
15
|
};
|
|
13
16
|
type MaybeArray<T> = T | Array<T>;
|
|
14
17
|
type PostHTMLNodeLike = PostHTML.Node | string;
|
|
18
|
+
type HtmlnanoTemplateRule = {
|
|
19
|
+
tag: string;
|
|
20
|
+
attrs?: Record<string, string | boolean | void>;
|
|
21
|
+
};
|
|
22
|
+
type MinifyHtmlTemplateOptions = boolean | HtmlnanoTemplateRule[];
|
|
15
23
|
interface HtmlnanoOptions {
|
|
16
24
|
skipConfigLoading?: boolean;
|
|
25
|
+
configPath?: string;
|
|
17
26
|
skipInternalWarnings?: boolean;
|
|
18
27
|
collapseAttributeWhitespace?: boolean;
|
|
19
28
|
collapseBooleanAttributes?: {
|
|
@@ -26,17 +35,42 @@ interface HtmlnanoOptions {
|
|
|
26
35
|
mergeStyles?: boolean;
|
|
27
36
|
mergeScripts?: boolean;
|
|
28
37
|
minifyCss?: Options | boolean;
|
|
38
|
+
minifyHtmlTemplate?: MinifyHtmlTemplateOptions;
|
|
29
39
|
minifyConditionalComments?: boolean;
|
|
30
40
|
minifyJs?: MinifyOptions | boolean;
|
|
31
41
|
minifyJson?: boolean;
|
|
42
|
+
minifyAttributes?: boolean | {
|
|
43
|
+
metaContent?: boolean;
|
|
44
|
+
redundantWhitespaces?: 'safe' | 'agressive' | false;
|
|
45
|
+
};
|
|
32
46
|
minifySvg?: Config | boolean;
|
|
33
47
|
normalizeAttributeValues?: boolean;
|
|
34
|
-
removeAttributeQuotes?: boolean
|
|
35
|
-
|
|
48
|
+
removeAttributeQuotes?: boolean | {
|
|
49
|
+
force?: boolean;
|
|
50
|
+
};
|
|
51
|
+
removeComments?: boolean | RegExp | ((comment: string) => boolean) | string;
|
|
36
52
|
removeEmptyAttributes?: boolean;
|
|
53
|
+
removeEmptyElements?: boolean | {
|
|
54
|
+
removeWithAttributes?: boolean;
|
|
55
|
+
};
|
|
37
56
|
removeRedundantAttributes?: boolean;
|
|
38
57
|
removeOptionalTags?: boolean;
|
|
39
|
-
removeUnusedCss?: boolean
|
|
58
|
+
removeUnusedCss?: boolean | ({
|
|
59
|
+
tool: 'purgeCSS';
|
|
60
|
+
} & Omit<UserDefinedOptions, 'content' | 'css' | 'extractors'>) | {
|
|
61
|
+
banner?: boolean;
|
|
62
|
+
csspath?: string;
|
|
63
|
+
htmlroot?: string;
|
|
64
|
+
ignore?: (string | RegExp)[];
|
|
65
|
+
inject?: string;
|
|
66
|
+
jsdom?: object;
|
|
67
|
+
media?: string[];
|
|
68
|
+
report?: boolean;
|
|
69
|
+
strictSSL?: boolean;
|
|
70
|
+
timeout?: number;
|
|
71
|
+
uncssrc?: string;
|
|
72
|
+
userAgent?: string;
|
|
73
|
+
};
|
|
40
74
|
sortAttributes?: boolean | 'alphabetical' | 'frequency';
|
|
41
75
|
sortAttributesWithLists?: boolean | 'alphabetical' | 'frequency';
|
|
42
76
|
}
|
|
@@ -2,18 +2,27 @@ import PostHTML from 'posthtml';
|
|
|
2
2
|
import { MinifyOptions } from 'terser';
|
|
3
3
|
import { Options } from 'cssnano';
|
|
4
4
|
import { Config } from 'svgo';
|
|
5
|
+
import { UserDefinedOptions } from 'purgecss';
|
|
5
6
|
|
|
6
7
|
type PostHTMLTreeLike = [PostHTML.Node] & PostHTML.NodeAPI & {
|
|
7
8
|
options?: {
|
|
8
9
|
quoteAllAttributes?: boolean | undefined;
|
|
10
|
+
quoteStyle?: 0 | 1 | 2 | undefined;
|
|
11
|
+
replaceQuote?: boolean | undefined;
|
|
9
12
|
} | undefined;
|
|
10
13
|
render(): string;
|
|
11
14
|
render(node: PostHTML.Node | PostHTMLTreeLike, renderOptions?: any): string;
|
|
12
15
|
};
|
|
13
16
|
type MaybeArray<T> = T | Array<T>;
|
|
14
17
|
type PostHTMLNodeLike = PostHTML.Node | string;
|
|
18
|
+
type HtmlnanoTemplateRule = {
|
|
19
|
+
tag: string;
|
|
20
|
+
attrs?: Record<string, string | boolean | void>;
|
|
21
|
+
};
|
|
22
|
+
type MinifyHtmlTemplateOptions = boolean | HtmlnanoTemplateRule[];
|
|
15
23
|
interface HtmlnanoOptions {
|
|
16
24
|
skipConfigLoading?: boolean;
|
|
25
|
+
configPath?: string;
|
|
17
26
|
skipInternalWarnings?: boolean;
|
|
18
27
|
collapseAttributeWhitespace?: boolean;
|
|
19
28
|
collapseBooleanAttributes?: {
|
|
@@ -26,17 +35,42 @@ interface HtmlnanoOptions {
|
|
|
26
35
|
mergeStyles?: boolean;
|
|
27
36
|
mergeScripts?: boolean;
|
|
28
37
|
minifyCss?: Options | boolean;
|
|
38
|
+
minifyHtmlTemplate?: MinifyHtmlTemplateOptions;
|
|
29
39
|
minifyConditionalComments?: boolean;
|
|
30
40
|
minifyJs?: MinifyOptions | boolean;
|
|
31
41
|
minifyJson?: boolean;
|
|
42
|
+
minifyAttributes?: boolean | {
|
|
43
|
+
metaContent?: boolean;
|
|
44
|
+
redundantWhitespaces?: 'safe' | 'agressive' | false;
|
|
45
|
+
};
|
|
32
46
|
minifySvg?: Config | boolean;
|
|
33
47
|
normalizeAttributeValues?: boolean;
|
|
34
|
-
removeAttributeQuotes?: boolean
|
|
35
|
-
|
|
48
|
+
removeAttributeQuotes?: boolean | {
|
|
49
|
+
force?: boolean;
|
|
50
|
+
};
|
|
51
|
+
removeComments?: boolean | RegExp | ((comment: string) => boolean) | string;
|
|
36
52
|
removeEmptyAttributes?: boolean;
|
|
53
|
+
removeEmptyElements?: boolean | {
|
|
54
|
+
removeWithAttributes?: boolean;
|
|
55
|
+
};
|
|
37
56
|
removeRedundantAttributes?: boolean;
|
|
38
57
|
removeOptionalTags?: boolean;
|
|
39
|
-
removeUnusedCss?: boolean
|
|
58
|
+
removeUnusedCss?: boolean | ({
|
|
59
|
+
tool: 'purgeCSS';
|
|
60
|
+
} & Omit<UserDefinedOptions, 'content' | 'css' | 'extractors'>) | {
|
|
61
|
+
banner?: boolean;
|
|
62
|
+
csspath?: string;
|
|
63
|
+
htmlroot?: string;
|
|
64
|
+
ignore?: (string | RegExp)[];
|
|
65
|
+
inject?: string;
|
|
66
|
+
jsdom?: object;
|
|
67
|
+
media?: string[];
|
|
68
|
+
report?: boolean;
|
|
69
|
+
strictSSL?: boolean;
|
|
70
|
+
timeout?: number;
|
|
71
|
+
uncssrc?: string;
|
|
72
|
+
userAgent?: string;
|
|
73
|
+
};
|
|
40
74
|
sortAttributes?: boolean | 'alphabetical' | 'frequency';
|
|
41
75
|
sortAttributesWithLists?: boolean | 'alphabetical' | 'frequency';
|
|
42
76
|
}
|
|
@@ -8,14 +8,15 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
8
8
|
var htmlnano__default = /*#__PURE__*/_interopDefault(htmlnano);
|
|
9
9
|
|
|
10
10
|
// Spec: https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/ms537512(v=vs.85)
|
|
11
|
-
const
|
|
11
|
+
const CONDITIONAL_COMMENT_HIDDEN_REGEXP = /(<!--\[if\s+?[^<>[\]]+?]>)([\s\S]*?)(<!\[endif\]-->)/gm;
|
|
12
|
+
const CONDITIONAL_COMMENT_REVEALED_REGEXP = /(<!--\[if\s+?[^<>[\]]+?\]><!-->)([\s\S]*?)(<!--<!\[endif\]-->)/gm;
|
|
12
13
|
async function minifyConditionalComments(tree, htmlnanoOptions) {
|
|
13
14
|
// forEach, tree.walk, tree.match just don't support Promise.
|
|
14
15
|
for(let i = 0, len = tree.length; i < len; i++){
|
|
15
16
|
const node = tree[i];
|
|
16
17
|
if (typeof node === 'string') {
|
|
17
18
|
if (helpers_js.isConditionalComment(node)) {
|
|
18
|
-
tree[i] = await
|
|
19
|
+
tree[i] = await minifyContentInsideConditionalComments(node, htmlnanoOptions);
|
|
19
20
|
}
|
|
20
21
|
} else if (node.content && node.content.length) {
|
|
21
22
|
node.content = await minifyConditionalComments(node.content, htmlnanoOptions);
|
|
@@ -26,29 +27,46 @@ async function minifyConditionalComments(tree, htmlnanoOptions) {
|
|
|
26
27
|
/** Minify content inside conditional comments */ const mod = {
|
|
27
28
|
default: minifyConditionalComments
|
|
28
29
|
};
|
|
29
|
-
|
|
30
|
-
let match;
|
|
30
|
+
function collectConditionalCommentMatches(text, regexp) {
|
|
31
31
|
const matches = [];
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
while((match =
|
|
35
|
-
matches.push(
|
|
36
|
-
match
|
|
37
|
-
match[
|
|
38
|
-
match[
|
|
39
|
-
|
|
32
|
+
regexp.lastIndex = 0;
|
|
33
|
+
let match;
|
|
34
|
+
while((match = regexp.exec(text)) !== null){
|
|
35
|
+
matches.push({
|
|
36
|
+
start: match.index,
|
|
37
|
+
end: match.index + match[0].length,
|
|
38
|
+
open: match[1],
|
|
39
|
+
content: match[2],
|
|
40
|
+
close: match[3]
|
|
41
|
+
});
|
|
40
42
|
}
|
|
43
|
+
return matches;
|
|
44
|
+
}
|
|
45
|
+
function hasHtmlOpeningWithoutClosing(content) {
|
|
46
|
+
return /<html\b/i.test(content) && !/<\/html>/i.test(content);
|
|
47
|
+
}
|
|
48
|
+
async function minifyContentInsideConditionalComments(text, htmlnanoOptions) {
|
|
49
|
+
const matches = [
|
|
50
|
+
...collectConditionalCommentMatches(text, CONDITIONAL_COMMENT_HIDDEN_REGEXP),
|
|
51
|
+
...collectConditionalCommentMatches(text, CONDITIONAL_COMMENT_REVEALED_REGEXP)
|
|
52
|
+
].sort((a, b)=>a.start - b.start);
|
|
41
53
|
if (!matches.length) {
|
|
42
54
|
return Promise.resolve(text);
|
|
43
55
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
let result = '';
|
|
57
|
+
let lastIndex = 0;
|
|
58
|
+
for (const match of matches){
|
|
59
|
+
result += text.slice(lastIndex, match.start);
|
|
60
|
+
const processed = await htmlnano__default.default.process(match.content, htmlnanoOptions, {}, {});
|
|
61
|
+
let minified = processed.html;
|
|
62
|
+
if (hasHtmlOpeningWithoutClosing(match.content) && /<\/html>/i.test(minified)) {
|
|
63
|
+
minified = minified.replace(/<\/html>/i, '');
|
|
49
64
|
}
|
|
50
|
-
|
|
51
|
-
|
|
65
|
+
result += match.open + minified + match.close;
|
|
66
|
+
lastIndex = match.end;
|
|
67
|
+
}
|
|
68
|
+
result += text.slice(lastIndex);
|
|
69
|
+
return result;
|
|
52
70
|
}
|
|
53
71
|
|
|
54
72
|
exports.default = mod;
|
|
@@ -2,14 +2,15 @@ import htmlnano from '../index.mjs';
|
|
|
2
2
|
import { isConditionalComment } from '../helpers.mjs';
|
|
3
3
|
|
|
4
4
|
// Spec: https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/ms537512(v=vs.85)
|
|
5
|
-
const
|
|
5
|
+
const CONDITIONAL_COMMENT_HIDDEN_REGEXP = /(<!--\[if\s+?[^<>[\]]+?]>)([\s\S]*?)(<!\[endif\]-->)/gm;
|
|
6
|
+
const CONDITIONAL_COMMENT_REVEALED_REGEXP = /(<!--\[if\s+?[^<>[\]]+?\]><!-->)([\s\S]*?)(<!--<!\[endif\]-->)/gm;
|
|
6
7
|
async function minifyConditionalComments(tree, htmlnanoOptions) {
|
|
7
8
|
// forEach, tree.walk, tree.match just don't support Promise.
|
|
8
9
|
for(let i = 0, len = tree.length; i < len; i++){
|
|
9
10
|
const node = tree[i];
|
|
10
11
|
if (typeof node === 'string') {
|
|
11
12
|
if (isConditionalComment(node)) {
|
|
12
|
-
tree[i] = await
|
|
13
|
+
tree[i] = await minifyContentInsideConditionalComments(node, htmlnanoOptions);
|
|
13
14
|
}
|
|
14
15
|
} else if (node.content && node.content.length) {
|
|
15
16
|
node.content = await minifyConditionalComments(node.content, htmlnanoOptions);
|
|
@@ -20,29 +21,46 @@ async function minifyConditionalComments(tree, htmlnanoOptions) {
|
|
|
20
21
|
/** Minify content inside conditional comments */ const mod = {
|
|
21
22
|
default: minifyConditionalComments
|
|
22
23
|
};
|
|
23
|
-
|
|
24
|
-
let match;
|
|
24
|
+
function collectConditionalCommentMatches(text, regexp) {
|
|
25
25
|
const matches = [];
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
while((match =
|
|
29
|
-
matches.push(
|
|
30
|
-
match
|
|
31
|
-
match[
|
|
32
|
-
match[
|
|
33
|
-
|
|
26
|
+
regexp.lastIndex = 0;
|
|
27
|
+
let match;
|
|
28
|
+
while((match = regexp.exec(text)) !== null){
|
|
29
|
+
matches.push({
|
|
30
|
+
start: match.index,
|
|
31
|
+
end: match.index + match[0].length,
|
|
32
|
+
open: match[1],
|
|
33
|
+
content: match[2],
|
|
34
|
+
close: match[3]
|
|
35
|
+
});
|
|
34
36
|
}
|
|
37
|
+
return matches;
|
|
38
|
+
}
|
|
39
|
+
function hasHtmlOpeningWithoutClosing(content) {
|
|
40
|
+
return /<html\b/i.test(content) && !/<\/html>/i.test(content);
|
|
41
|
+
}
|
|
42
|
+
async function minifyContentInsideConditionalComments(text, htmlnanoOptions) {
|
|
43
|
+
const matches = [
|
|
44
|
+
...collectConditionalCommentMatches(text, CONDITIONAL_COMMENT_HIDDEN_REGEXP),
|
|
45
|
+
...collectConditionalCommentMatches(text, CONDITIONAL_COMMENT_REVEALED_REGEXP)
|
|
46
|
+
].sort((a, b)=>a.start - b.start);
|
|
35
47
|
if (!matches.length) {
|
|
36
48
|
return Promise.resolve(text);
|
|
37
49
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
50
|
+
let result = '';
|
|
51
|
+
let lastIndex = 0;
|
|
52
|
+
for (const match of matches){
|
|
53
|
+
result += text.slice(lastIndex, match.start);
|
|
54
|
+
const processed = await htmlnano.process(match.content, htmlnanoOptions, {}, {});
|
|
55
|
+
let minified = processed.html;
|
|
56
|
+
if (hasHtmlOpeningWithoutClosing(match.content) && /<\/html>/i.test(minified)) {
|
|
57
|
+
minified = minified.replace(/<\/html>/i, '');
|
|
43
58
|
}
|
|
44
|
-
|
|
45
|
-
|
|
59
|
+
result += match.open + minified + match.close;
|
|
60
|
+
lastIndex = match.end;
|
|
61
|
+
}
|
|
62
|
+
result += text.slice(lastIndex);
|
|
63
|
+
return result;
|
|
46
64
|
}
|
|
47
65
|
|
|
48
66
|
export { mod as default };
|