htmlnano 3.0.0 → 3.2.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 +40 -25
- package/dist/_modules/collapseAttributeWhitespace.d.mts +41 -5
- package/dist/_modules/collapseAttributeWhitespace.d.ts +41 -5
- package/dist/_modules/collapseAttributeWhitespace.js +69 -16
- package/dist/_modules/collapseAttributeWhitespace.mjs +67 -17
- package/dist/_modules/collapseBooleanAttributes.d.mts +36 -3
- package/dist/_modules/collapseBooleanAttributes.d.ts +36 -3
- package/dist/_modules/collapseBooleanAttributes.js +18 -11
- package/dist/_modules/collapseBooleanAttributes.mjs +18 -11
- package/dist/_modules/collapseWhitespace.d.mts +36 -3
- package/dist/_modules/collapseWhitespace.d.ts +36 -3
- package/dist/_modules/collapseWhitespace.js +25 -2
- package/dist/_modules/collapseWhitespace.mjs +25 -2
- package/dist/_modules/custom.d.mts +36 -3
- package/dist/_modules/custom.d.ts +36 -3
- package/dist/_modules/deduplicateAttributeValues.d.mts +36 -3
- package/dist/_modules/deduplicateAttributeValues.d.ts +36 -3
- package/dist/_modules/deduplicateAttributeValues.js +20 -5
- package/dist/_modules/deduplicateAttributeValues.mjs +21 -6
- package/dist/_modules/example.d.mts +36 -3
- package/dist/_modules/example.d.ts +36 -3
- package/dist/_modules/mergeScripts.d.mts +36 -3
- package/dist/_modules/mergeScripts.d.ts +36 -3
- package/dist/_modules/mergeScripts.js +111 -24
- package/dist/_modules/mergeScripts.mjs +111 -24
- package/dist/_modules/mergeStyles.d.mts +36 -3
- package/dist/_modules/mergeStyles.d.ts +36 -3
- package/dist/_modules/mergeStyles.js +66 -4
- package/dist/_modules/mergeStyles.mjs +66 -4
- 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 +36 -3
- package/dist/_modules/minifyConditionalComments.d.ts +36 -3
- package/dist/_modules/minifyConditionalComments.js +37 -19
- package/dist/_modules/minifyConditionalComments.mjs +37 -19
- package/dist/_modules/minifyCss.d.mts +36 -3
- package/dist/_modules/minifyCss.d.ts +36 -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 +36 -3
- package/dist/_modules/minifyJs.d.ts +36 -3
- package/dist/_modules/minifyJs.js +106 -5
- package/dist/_modules/minifyJs.mjs +107 -6
- package/dist/_modules/minifyJson.d.mts +36 -3
- package/dist/_modules/minifyJson.d.ts +36 -3
- package/dist/_modules/minifyJson.js +8 -11
- package/dist/_modules/minifyJson.mjs +8 -11
- package/dist/_modules/minifySvg.d.mts +36 -3
- package/dist/_modules/minifySvg.d.ts +36 -3
- package/dist/_modules/minifySvg.js +35 -4
- package/dist/_modules/minifySvg.mjs +35 -4
- package/dist/_modules/minifyUrls.d.mts +37 -4
- package/dist/_modules/minifyUrls.d.ts +37 -4
- package/dist/_modules/minifyUrls.js +52 -27
- package/dist/_modules/minifyUrls.mjs +52 -27
- package/dist/_modules/normalizeAttributeValues.d.mts +36 -3
- package/dist/_modules/normalizeAttributeValues.d.ts +36 -3
- package/dist/_modules/normalizeAttributeValues.js +10 -8
- package/dist/_modules/normalizeAttributeValues.mjs +10 -8
- package/dist/_modules/removeAttributeQuotes.d.mts +40 -4
- package/dist/_modules/removeAttributeQuotes.d.ts +40 -4
- package/dist/_modules/removeAttributeQuotes.js +9 -4
- package/dist/_modules/removeAttributeQuotes.mjs +9 -4
- package/dist/_modules/removeComments.d.mts +37 -4
- package/dist/_modules/removeComments.d.ts +37 -4
- package/dist/_modules/removeComments.js +44 -12
- package/dist/_modules/removeComments.mjs +44 -12
- package/dist/_modules/removeEmptyAttributes.d.mts +36 -3
- package/dist/_modules/removeEmptyAttributes.d.ts +36 -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 +36 -3
- package/dist/_modules/removeOptionalTags.d.ts +36 -3
- package/dist/_modules/removeOptionalTags.js +39 -28
- package/dist/_modules/removeOptionalTags.mjs +39 -28
- package/dist/_modules/removeRedundantAttributes.d.mts +36 -3
- package/dist/_modules/removeRedundantAttributes.d.ts +36 -3
- package/dist/_modules/removeRedundantAttributes.js +43 -28
- package/dist/_modules/removeRedundantAttributes.mjs +43 -28
- package/dist/_modules/removeUnusedCss.d.mts +37 -3
- package/dist/_modules/removeUnusedCss.d.ts +37 -3
- package/dist/_modules/removeUnusedCss.js +40 -14
- package/dist/_modules/removeUnusedCss.mjs +41 -15
- package/dist/_modules/sortAttributes.d.mts +39 -5
- package/dist/_modules/sortAttributes.d.ts +39 -5
- package/dist/_modules/sortAttributes.js +27 -8
- package/dist/_modules/sortAttributes.mjs +27 -8
- package/dist/_modules/sortAttributesWithLists.d.mts +39 -4
- package/dist/_modules/sortAttributesWithLists.d.ts +39 -4
- package/dist/_modules/sortAttributesWithLists.js +61 -52
- package/dist/_modules/sortAttributesWithLists.mjs +62 -53
- package/dist/helpers.d.ts +8 -1
- package/dist/helpers.js +48 -0
- package/dist/helpers.mjs +45 -1
- package/dist/index.d.ts +37 -4
- package/dist/index.js +13 -0
- package/dist/index.mjs +13 -0
- package/dist/presets/ampSafe.d.ts +36 -3
- package/dist/presets/max.d.ts +36 -3
- package/dist/presets/max.js +13 -5
- package/dist/presets/max.mjs +13 -5
- package/dist/presets/safe.d.ts +36 -3
- package/dist/presets/safe.js +6 -0
- package/dist/presets/safe.mjs +6 -0
- package/package.json +27 -15
|
@@ -1,49 +1,136 @@
|
|
|
1
1
|
import { extractTextContentFromNode } from '../helpers.mjs';
|
|
2
2
|
|
|
3
|
+
function normalizeAttrsForKey(attrs, config) {
|
|
4
|
+
const normalized = {
|
|
5
|
+
...config.baseAttrs
|
|
6
|
+
};
|
|
7
|
+
for (const [key, value] of Object.entries(attrs || {})){
|
|
8
|
+
if (config.skippedAttrs.has(key) || value === undefined) {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
if (config.booleanAttrs.has(key)) {
|
|
12
|
+
normalized[key] = true;
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
normalized[key] = value;
|
|
16
|
+
}
|
|
17
|
+
return normalized;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeAsyncAttr(attrs) {
|
|
21
|
+
if (!attrs) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (attrs.async === '') {
|
|
25
|
+
attrs.async = true;
|
|
26
|
+
}
|
|
27
|
+
if (attrs.nomodule === '') {
|
|
28
|
+
attrs.nomodule = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function getScriptType(attrs) {
|
|
32
|
+
const type = attrs.type || 'text/javascript';
|
|
33
|
+
return typeof type === 'string' ? type.toLowerCase() : 'text/javascript';
|
|
34
|
+
}
|
|
35
|
+
function isMergeableScriptType(type) {
|
|
36
|
+
return type === 'text/javascript' || type === 'application/javascript';
|
|
37
|
+
}
|
|
38
|
+
const booleanAttrs = new Set([
|
|
39
|
+
'async',
|
|
40
|
+
'defer',
|
|
41
|
+
'nomodule'
|
|
42
|
+
]);
|
|
43
|
+
const skippedAttrs = new Set([
|
|
44
|
+
'src',
|
|
45
|
+
'integrity',
|
|
46
|
+
'type'
|
|
47
|
+
]);
|
|
48
|
+
function normalizeScriptAttrsForKey(attrs, scriptType) {
|
|
49
|
+
return normalizeAttrsForKey(attrs, {
|
|
50
|
+
baseAttrs: {
|
|
51
|
+
type: scriptType
|
|
52
|
+
},
|
|
53
|
+
booleanAttrs,
|
|
54
|
+
skippedAttrs
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function buildScriptKey(attrs, scriptType, scriptSrcIndex) {
|
|
58
|
+
const normalizedAttrs = normalizeScriptAttrsForKey(attrs, scriptType);
|
|
59
|
+
const keyObject = {
|
|
60
|
+
index: scriptSrcIndex,
|
|
61
|
+
...normalizedAttrs
|
|
62
|
+
};
|
|
63
|
+
const sortedKeys = Object.keys(keyObject).sort();
|
|
64
|
+
const sortedKeyObject = {};
|
|
65
|
+
for (const key of sortedKeys){
|
|
66
|
+
sortedKeyObject[key] = keyObject[key];
|
|
67
|
+
}
|
|
68
|
+
return JSON.stringify(sortedKeyObject);
|
|
69
|
+
}
|
|
70
|
+
function endsWithLineComment(scriptContent) {
|
|
71
|
+
const lastNewlineIndex = Math.max(scriptContent.lastIndexOf('\n'), scriptContent.lastIndexOf('\r'));
|
|
72
|
+
const lastLine = lastNewlineIndex === -1 ? scriptContent : scriptContent.slice(lastNewlineIndex + 1);
|
|
73
|
+
return /\/\/.*$/.test(lastLine);
|
|
74
|
+
}
|
|
75
|
+
function mergeScriptNodes(scriptNodesIndex, tracking) {
|
|
76
|
+
for (const scriptNodes of Object.values(scriptNodesIndex)){
|
|
77
|
+
if (scriptNodes.length < 2) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const lastScriptNode = scriptNodes.pop();
|
|
81
|
+
tracking.mergedScriptNodes.add(lastScriptNode);
|
|
82
|
+
scriptNodes.reverse().forEach((scriptNode)=>{
|
|
83
|
+
let scriptContent = extractTextContentFromNode(scriptNode).trim();
|
|
84
|
+
if (!scriptContent) {
|
|
85
|
+
tracking.removedScriptNodes.add(scriptNode);
|
|
86
|
+
// @ts-expect-error -- remove node
|
|
87
|
+
scriptNode.tag = false;
|
|
88
|
+
scriptNode.content = [];
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (endsWithLineComment(scriptContent)) {
|
|
92
|
+
scriptContent += '\n;';
|
|
93
|
+
} else if (scriptContent.slice(-1) !== ';') {
|
|
94
|
+
scriptContent += ';';
|
|
95
|
+
}
|
|
96
|
+
lastScriptNode.content = lastScriptNode.content || [];
|
|
97
|
+
lastScriptNode.content.unshift(scriptContent);
|
|
98
|
+
tracking.removedScriptNodes.add(scriptNode);
|
|
99
|
+
// @ts-expect-error -- remove node
|
|
100
|
+
scriptNode.tag = false;
|
|
101
|
+
scriptNode.content = [];
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
3
105
|
/* Merge multiple <script> into one */ const mod = {
|
|
4
106
|
default (tree) {
|
|
5
107
|
const scriptNodesIndex = {};
|
|
108
|
+
const tracking = {
|
|
109
|
+
mergedScriptNodes: new WeakSet(),
|
|
110
|
+
removedScriptNodes: new WeakSet()
|
|
111
|
+
};
|
|
6
112
|
let scriptSrcIndex = 1;
|
|
7
113
|
tree.match({
|
|
8
114
|
tag: 'script'
|
|
9
115
|
}, (node)=>{
|
|
10
116
|
const nodeAttrs = node.attrs || {};
|
|
117
|
+
normalizeAsyncAttr(nodeAttrs);
|
|
11
118
|
if ('src' in nodeAttrs || 'integrity' in nodeAttrs) {
|
|
12
119
|
scriptSrcIndex++;
|
|
13
120
|
return node;
|
|
14
121
|
}
|
|
15
|
-
const scriptType = nodeAttrs
|
|
16
|
-
if (scriptType
|
|
122
|
+
const scriptType = getScriptType(nodeAttrs);
|
|
123
|
+
if (!isMergeableScriptType(scriptType)) {
|
|
17
124
|
return node;
|
|
18
125
|
}
|
|
19
|
-
const scriptKey =
|
|
20
|
-
id: nodeAttrs.id,
|
|
21
|
-
class: nodeAttrs.class,
|
|
22
|
-
type: scriptType,
|
|
23
|
-
defer: nodeAttrs.defer !== undefined,
|
|
24
|
-
async: nodeAttrs.async !== undefined,
|
|
25
|
-
index: scriptSrcIndex
|
|
26
|
-
});
|
|
126
|
+
const scriptKey = buildScriptKey(nodeAttrs, scriptType, scriptSrcIndex);
|
|
27
127
|
if (!scriptNodesIndex[scriptKey]) {
|
|
28
128
|
scriptNodesIndex[scriptKey] = [];
|
|
29
129
|
}
|
|
30
130
|
scriptNodesIndex[scriptKey].push(node);
|
|
31
131
|
return node;
|
|
32
132
|
});
|
|
33
|
-
|
|
34
|
-
const lastScriptNode = scriptNodes.pop();
|
|
35
|
-
scriptNodes.reverse().forEach((scriptNode)=>{
|
|
36
|
-
let scriptContent = extractTextContentFromNode(scriptNode).trim();
|
|
37
|
-
if (scriptContent.slice(-1) !== ';') {
|
|
38
|
-
scriptContent += ';';
|
|
39
|
-
}
|
|
40
|
-
lastScriptNode.content = lastScriptNode.content || [];
|
|
41
|
-
lastScriptNode.content.unshift(scriptContent);
|
|
42
|
-
// @ts-expect-error -- remove node
|
|
43
|
-
scriptNode.tag = false;
|
|
44
|
-
scriptNode.content = [];
|
|
45
|
-
});
|
|
46
|
-
}
|
|
133
|
+
mergeScriptNodes(scriptNodesIndex, tracking);
|
|
47
134
|
return tree;
|
|
48
135
|
}
|
|
49
136
|
};
|
|
@@ -2,16 +2,24 @@ 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;
|
|
17
25
|
configPath?: string;
|
|
@@ -27,17 +35,42 @@ interface HtmlnanoOptions {
|
|
|
27
35
|
mergeStyles?: boolean;
|
|
28
36
|
mergeScripts?: boolean;
|
|
29
37
|
minifyCss?: Options | boolean;
|
|
38
|
+
minifyHtmlTemplate?: MinifyHtmlTemplateOptions;
|
|
30
39
|
minifyConditionalComments?: boolean;
|
|
31
40
|
minifyJs?: MinifyOptions | boolean;
|
|
32
41
|
minifyJson?: boolean;
|
|
42
|
+
minifyAttributes?: boolean | {
|
|
43
|
+
metaContent?: boolean;
|
|
44
|
+
redundantWhitespaces?: 'safe' | 'agressive' | false;
|
|
45
|
+
};
|
|
33
46
|
minifySvg?: Config | boolean;
|
|
34
47
|
normalizeAttributeValues?: boolean;
|
|
35
|
-
removeAttributeQuotes?: boolean
|
|
36
|
-
|
|
48
|
+
removeAttributeQuotes?: boolean | {
|
|
49
|
+
force?: boolean;
|
|
50
|
+
};
|
|
51
|
+
removeComments?: boolean | RegExp | ((comment: string) => boolean) | string;
|
|
37
52
|
removeEmptyAttributes?: boolean;
|
|
53
|
+
removeEmptyElements?: boolean | {
|
|
54
|
+
removeWithAttributes?: boolean;
|
|
55
|
+
};
|
|
38
56
|
removeRedundantAttributes?: boolean;
|
|
39
57
|
removeOptionalTags?: boolean;
|
|
40
|
-
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
|
+
};
|
|
41
74
|
sortAttributes?: boolean | 'alphabetical' | 'frequency';
|
|
42
75
|
sortAttributesWithLists?: boolean | 'alphabetical' | 'frequency';
|
|
43
76
|
}
|
|
@@ -2,16 +2,24 @@ 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;
|
|
17
25
|
configPath?: string;
|
|
@@ -27,17 +35,42 @@ interface HtmlnanoOptions {
|
|
|
27
35
|
mergeStyles?: boolean;
|
|
28
36
|
mergeScripts?: boolean;
|
|
29
37
|
minifyCss?: Options | boolean;
|
|
38
|
+
minifyHtmlTemplate?: MinifyHtmlTemplateOptions;
|
|
30
39
|
minifyConditionalComments?: boolean;
|
|
31
40
|
minifyJs?: MinifyOptions | boolean;
|
|
32
41
|
minifyJson?: boolean;
|
|
42
|
+
minifyAttributes?: boolean | {
|
|
43
|
+
metaContent?: boolean;
|
|
44
|
+
redundantWhitespaces?: 'safe' | 'agressive' | false;
|
|
45
|
+
};
|
|
33
46
|
minifySvg?: Config | boolean;
|
|
34
47
|
normalizeAttributeValues?: boolean;
|
|
35
|
-
removeAttributeQuotes?: boolean
|
|
36
|
-
|
|
48
|
+
removeAttributeQuotes?: boolean | {
|
|
49
|
+
force?: boolean;
|
|
50
|
+
};
|
|
51
|
+
removeComments?: boolean | RegExp | ((comment: string) => boolean) | string;
|
|
37
52
|
removeEmptyAttributes?: boolean;
|
|
53
|
+
removeEmptyElements?: boolean | {
|
|
54
|
+
removeWithAttributes?: boolean;
|
|
55
|
+
};
|
|
38
56
|
removeRedundantAttributes?: boolean;
|
|
39
57
|
removeOptionalTags?: boolean;
|
|
40
|
-
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
|
+
};
|
|
41
74
|
sortAttributes?: boolean | 'alphabetical' | 'frequency';
|
|
42
75
|
sortAttributesWithLists?: boolean | 'alphabetical' | 'frequency';
|
|
43
76
|
}
|
|
@@ -2,6 +2,70 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
2
2
|
|
|
3
3
|
var helpers_js = require('../helpers.js');
|
|
4
4
|
|
|
5
|
+
function normalizeAttrsForKey(attrs, config) {
|
|
6
|
+
const normalized = {
|
|
7
|
+
...config.baseAttrs
|
|
8
|
+
};
|
|
9
|
+
for (const [key, value] of Object.entries(attrs || {})){
|
|
10
|
+
if (config.skippedAttrs.has(key) || value === undefined) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
if (config.booleanAttrs.has(key)) {
|
|
14
|
+
normalized[key] = true;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
normalized[key] = value;
|
|
18
|
+
}
|
|
19
|
+
return normalized;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const booleanAttrs = new Set([
|
|
23
|
+
'amp-custom',
|
|
24
|
+
'disabled'
|
|
25
|
+
]);
|
|
26
|
+
const skippedAttrs = new Set([
|
|
27
|
+
'type',
|
|
28
|
+
'media'
|
|
29
|
+
]);
|
|
30
|
+
function normalizeStyleType(attrs) {
|
|
31
|
+
if (!attrs || typeof attrs.type !== 'string') {
|
|
32
|
+
return 'text/css';
|
|
33
|
+
}
|
|
34
|
+
const type = attrs.type.trim();
|
|
35
|
+
return type ? type.toLowerCase() : 'text/css';
|
|
36
|
+
}
|
|
37
|
+
function normalizeStyleMedia(attrs) {
|
|
38
|
+
if (!attrs || typeof attrs.media !== 'string') {
|
|
39
|
+
return 'all';
|
|
40
|
+
}
|
|
41
|
+
const media = attrs.media.trim();
|
|
42
|
+
return media ? media.replace(/\s+/g, ' ').toLowerCase() : 'all';
|
|
43
|
+
}
|
|
44
|
+
function normalizeStyleAttrsForKey(attrs) {
|
|
45
|
+
return normalizeAttrsForKey(attrs, {
|
|
46
|
+
booleanAttrs,
|
|
47
|
+
skippedAttrs
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function buildStyleKey(attrs) {
|
|
51
|
+
const keyObject = {
|
|
52
|
+
type: normalizeStyleType(attrs),
|
|
53
|
+
media: normalizeStyleMedia(attrs),
|
|
54
|
+
...normalizeStyleAttrsForKey(attrs)
|
|
55
|
+
};
|
|
56
|
+
const sortedKeys = Object.keys(keyObject).sort();
|
|
57
|
+
const sortedKeyObject = {};
|
|
58
|
+
for (const key of sortedKeys){
|
|
59
|
+
sortedKeyObject[key] = keyObject[key];
|
|
60
|
+
}
|
|
61
|
+
return JSON.stringify(sortedKeyObject);
|
|
62
|
+
}
|
|
63
|
+
function extractStyleTextContent(node) {
|
|
64
|
+
if (typeof node.content === 'string') {
|
|
65
|
+
return node.content;
|
|
66
|
+
}
|
|
67
|
+
return helpers_js.extractTextContentFromNode(node);
|
|
68
|
+
}
|
|
5
69
|
/* Merge multiple <style> into one */ const mod = {
|
|
6
70
|
default (tree) {
|
|
7
71
|
const styleNodes = {};
|
|
@@ -20,12 +84,10 @@ var helpers_js = require('../helpers.js');
|
|
|
20
84
|
if (helpers_js.isAmpBoilerplate(node)) {
|
|
21
85
|
return node;
|
|
22
86
|
}
|
|
23
|
-
const
|
|
24
|
-
const styleMedia = nodeAttrs.media || 'all';
|
|
25
|
-
const styleKey = styleType + '_' + styleMedia;
|
|
87
|
+
const styleKey = buildStyleKey(nodeAttrs);
|
|
26
88
|
if (styleKey in styleNodes) {
|
|
27
89
|
var _styleNodes_styleKey, _content;
|
|
28
|
-
const styleContent =
|
|
90
|
+
const styleContent = extractStyleTextContent(node);
|
|
29
91
|
(_content = (_styleNodes_styleKey = styleNodes[styleKey]).content) != null ? _content : _styleNodes_styleKey.content = [];
|
|
30
92
|
styleNodes[styleKey].content.push(' ' + styleContent);
|
|
31
93
|
return ''; // Remove node
|
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
import { isAmpBoilerplate, extractTextContentFromNode } from '../helpers.mjs';
|
|
2
2
|
|
|
3
|
+
function normalizeAttrsForKey(attrs, config) {
|
|
4
|
+
const normalized = {
|
|
5
|
+
...config.baseAttrs
|
|
6
|
+
};
|
|
7
|
+
for (const [key, value] of Object.entries(attrs || {})){
|
|
8
|
+
if (config.skippedAttrs.has(key) || value === undefined) {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
if (config.booleanAttrs.has(key)) {
|
|
12
|
+
normalized[key] = true;
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
normalized[key] = value;
|
|
16
|
+
}
|
|
17
|
+
return normalized;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const booleanAttrs = new Set([
|
|
21
|
+
'amp-custom',
|
|
22
|
+
'disabled'
|
|
23
|
+
]);
|
|
24
|
+
const skippedAttrs = new Set([
|
|
25
|
+
'type',
|
|
26
|
+
'media'
|
|
27
|
+
]);
|
|
28
|
+
function normalizeStyleType(attrs) {
|
|
29
|
+
if (!attrs || typeof attrs.type !== 'string') {
|
|
30
|
+
return 'text/css';
|
|
31
|
+
}
|
|
32
|
+
const type = attrs.type.trim();
|
|
33
|
+
return type ? type.toLowerCase() : 'text/css';
|
|
34
|
+
}
|
|
35
|
+
function normalizeStyleMedia(attrs) {
|
|
36
|
+
if (!attrs || typeof attrs.media !== 'string') {
|
|
37
|
+
return 'all';
|
|
38
|
+
}
|
|
39
|
+
const media = attrs.media.trim();
|
|
40
|
+
return media ? media.replace(/\s+/g, ' ').toLowerCase() : 'all';
|
|
41
|
+
}
|
|
42
|
+
function normalizeStyleAttrsForKey(attrs) {
|
|
43
|
+
return normalizeAttrsForKey(attrs, {
|
|
44
|
+
booleanAttrs,
|
|
45
|
+
skippedAttrs
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function buildStyleKey(attrs) {
|
|
49
|
+
const keyObject = {
|
|
50
|
+
type: normalizeStyleType(attrs),
|
|
51
|
+
media: normalizeStyleMedia(attrs),
|
|
52
|
+
...normalizeStyleAttrsForKey(attrs)
|
|
53
|
+
};
|
|
54
|
+
const sortedKeys = Object.keys(keyObject).sort();
|
|
55
|
+
const sortedKeyObject = {};
|
|
56
|
+
for (const key of sortedKeys){
|
|
57
|
+
sortedKeyObject[key] = keyObject[key];
|
|
58
|
+
}
|
|
59
|
+
return JSON.stringify(sortedKeyObject);
|
|
60
|
+
}
|
|
61
|
+
function extractStyleTextContent(node) {
|
|
62
|
+
if (typeof node.content === 'string') {
|
|
63
|
+
return node.content;
|
|
64
|
+
}
|
|
65
|
+
return extractTextContentFromNode(node);
|
|
66
|
+
}
|
|
3
67
|
/* Merge multiple <style> into one */ const mod = {
|
|
4
68
|
default (tree) {
|
|
5
69
|
const styleNodes = {};
|
|
@@ -18,12 +82,10 @@ import { isAmpBoilerplate, extractTextContentFromNode } from '../helpers.mjs';
|
|
|
18
82
|
if (isAmpBoilerplate(node)) {
|
|
19
83
|
return node;
|
|
20
84
|
}
|
|
21
|
-
const
|
|
22
|
-
const styleMedia = nodeAttrs.media || 'all';
|
|
23
|
-
const styleKey = styleType + '_' + styleMedia;
|
|
85
|
+
const styleKey = buildStyleKey(nodeAttrs);
|
|
24
86
|
if (styleKey in styleNodes) {
|
|
25
87
|
var _styleNodes_styleKey, _content;
|
|
26
|
-
const styleContent =
|
|
88
|
+
const styleContent = extractStyleTextContent(node);
|
|
27
89
|
(_content = (_styleNodes_styleKey = styleNodes[styleKey]).content) != null ? _content : _styleNodes_styleKey.content = [];
|
|
28
90
|
styleNodes[styleKey].content.push(' ' + styleContent);
|
|
29
91
|
return ''; // Remove node
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import PostHTML from 'posthtml';
|
|
2
|
+
import { MinifyOptions } from 'terser';
|
|
3
|
+
import { Options } from 'cssnano';
|
|
4
|
+
import { Config } from 'svgo';
|
|
5
|
+
import { UserDefinedOptions } from 'purgecss';
|
|
6
|
+
|
|
7
|
+
type PostHTMLTreeLike = [PostHTML.Node] & PostHTML.NodeAPI & {
|
|
8
|
+
options?: {
|
|
9
|
+
quoteAllAttributes?: boolean | undefined;
|
|
10
|
+
quoteStyle?: 0 | 1 | 2 | undefined;
|
|
11
|
+
replaceQuote?: boolean | undefined;
|
|
12
|
+
} | undefined;
|
|
13
|
+
render(): string;
|
|
14
|
+
render(node: PostHTML.Node | PostHTMLTreeLike, renderOptions?: any): string;
|
|
15
|
+
};
|
|
16
|
+
type MaybeArray<T> = T | Array<T>;
|
|
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[];
|
|
23
|
+
interface HtmlnanoOptions {
|
|
24
|
+
skipConfigLoading?: boolean;
|
|
25
|
+
configPath?: string;
|
|
26
|
+
skipInternalWarnings?: boolean;
|
|
27
|
+
collapseAttributeWhitespace?: boolean;
|
|
28
|
+
collapseBooleanAttributes?: {
|
|
29
|
+
amphtml?: boolean;
|
|
30
|
+
};
|
|
31
|
+
collapseWhitespace?: 'conservative' | 'all' | 'aggressive';
|
|
32
|
+
custom?: MaybeArray<(tree: PostHTMLTreeLike, options?: any) => (PostHTML.Node | PostHTMLTreeLike)>;
|
|
33
|
+
deduplicateAttributeValues?: boolean;
|
|
34
|
+
minifyUrls?: URL | string | false;
|
|
35
|
+
mergeStyles?: boolean;
|
|
36
|
+
mergeScripts?: boolean;
|
|
37
|
+
minifyCss?: Options | boolean;
|
|
38
|
+
minifyHtmlTemplate?: MinifyHtmlTemplateOptions;
|
|
39
|
+
minifyConditionalComments?: boolean;
|
|
40
|
+
minifyJs?: MinifyOptions | boolean;
|
|
41
|
+
minifyJson?: boolean;
|
|
42
|
+
minifyAttributes?: boolean | {
|
|
43
|
+
metaContent?: boolean;
|
|
44
|
+
redundantWhitespaces?: 'safe' | 'agressive' | false;
|
|
45
|
+
};
|
|
46
|
+
minifySvg?: Config | boolean;
|
|
47
|
+
normalizeAttributeValues?: boolean;
|
|
48
|
+
removeAttributeQuotes?: boolean | {
|
|
49
|
+
force?: boolean;
|
|
50
|
+
};
|
|
51
|
+
removeComments?: boolean | RegExp | ((comment: string) => boolean) | string;
|
|
52
|
+
removeEmptyAttributes?: boolean;
|
|
53
|
+
removeEmptyElements?: boolean | {
|
|
54
|
+
removeWithAttributes?: boolean;
|
|
55
|
+
};
|
|
56
|
+
removeRedundantAttributes?: boolean;
|
|
57
|
+
removeOptionalTags?: 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
|
+
};
|
|
74
|
+
sortAttributes?: boolean | 'alphabetical' | 'frequency';
|
|
75
|
+
sortAttributesWithLists?: boolean | 'alphabetical' | 'frequency';
|
|
76
|
+
}
|
|
77
|
+
type HtmlnanoModuleAttrsHandler = (attrs: Record<string, string | boolean | void>, node: PostHTML.Node) => Record<string, string | boolean | void>;
|
|
78
|
+
type HtmlnanoModuleContentHandler = (content: Array<PostHTMLNodeLike>, node: PostHTML.Node) => MaybeArray<PostHTMLNodeLike>;
|
|
79
|
+
type HtmlnanoModuleNodeHandler = (node: PostHTMLNodeLike) => PostHTML.Node | string;
|
|
80
|
+
type OptionalOptions<T> = T extends boolean | string | Function | number | null | undefined ? T : T extends object ? Partial<T> : T;
|
|
81
|
+
type HtmlnanoModule<Options = any> = {
|
|
82
|
+
onAttrs?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleAttrsHandler;
|
|
83
|
+
onContent?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleContentHandler;
|
|
84
|
+
onNode?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleNodeHandler;
|
|
85
|
+
default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
type RedundantWhitespaceMode = 'safe' | 'agressive' | false;
|
|
89
|
+
type MinifyAttributesOptions = {
|
|
90
|
+
metaContent?: boolean;
|
|
91
|
+
redundantWhitespaces?: RedundantWhitespaceMode | 'aggressive';
|
|
92
|
+
};
|
|
93
|
+
declare const mod: HtmlnanoModule<MinifyAttributesOptions>;
|
|
94
|
+
|
|
95
|
+
export { mod as default };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import PostHTML from 'posthtml';
|
|
2
|
+
import { MinifyOptions } from 'terser';
|
|
3
|
+
import { Options } from 'cssnano';
|
|
4
|
+
import { Config } from 'svgo';
|
|
5
|
+
import { UserDefinedOptions } from 'purgecss';
|
|
6
|
+
|
|
7
|
+
type PostHTMLTreeLike = [PostHTML.Node] & PostHTML.NodeAPI & {
|
|
8
|
+
options?: {
|
|
9
|
+
quoteAllAttributes?: boolean | undefined;
|
|
10
|
+
quoteStyle?: 0 | 1 | 2 | undefined;
|
|
11
|
+
replaceQuote?: boolean | undefined;
|
|
12
|
+
} | undefined;
|
|
13
|
+
render(): string;
|
|
14
|
+
render(node: PostHTML.Node | PostHTMLTreeLike, renderOptions?: any): string;
|
|
15
|
+
};
|
|
16
|
+
type MaybeArray<T> = T | Array<T>;
|
|
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[];
|
|
23
|
+
interface HtmlnanoOptions {
|
|
24
|
+
skipConfigLoading?: boolean;
|
|
25
|
+
configPath?: string;
|
|
26
|
+
skipInternalWarnings?: boolean;
|
|
27
|
+
collapseAttributeWhitespace?: boolean;
|
|
28
|
+
collapseBooleanAttributes?: {
|
|
29
|
+
amphtml?: boolean;
|
|
30
|
+
};
|
|
31
|
+
collapseWhitespace?: 'conservative' | 'all' | 'aggressive';
|
|
32
|
+
custom?: MaybeArray<(tree: PostHTMLTreeLike, options?: any) => (PostHTML.Node | PostHTMLTreeLike)>;
|
|
33
|
+
deduplicateAttributeValues?: boolean;
|
|
34
|
+
minifyUrls?: URL | string | false;
|
|
35
|
+
mergeStyles?: boolean;
|
|
36
|
+
mergeScripts?: boolean;
|
|
37
|
+
minifyCss?: Options | boolean;
|
|
38
|
+
minifyHtmlTemplate?: MinifyHtmlTemplateOptions;
|
|
39
|
+
minifyConditionalComments?: boolean;
|
|
40
|
+
minifyJs?: MinifyOptions | boolean;
|
|
41
|
+
minifyJson?: boolean;
|
|
42
|
+
minifyAttributes?: boolean | {
|
|
43
|
+
metaContent?: boolean;
|
|
44
|
+
redundantWhitespaces?: 'safe' | 'agressive' | false;
|
|
45
|
+
};
|
|
46
|
+
minifySvg?: Config | boolean;
|
|
47
|
+
normalizeAttributeValues?: boolean;
|
|
48
|
+
removeAttributeQuotes?: boolean | {
|
|
49
|
+
force?: boolean;
|
|
50
|
+
};
|
|
51
|
+
removeComments?: boolean | RegExp | ((comment: string) => boolean) | string;
|
|
52
|
+
removeEmptyAttributes?: boolean;
|
|
53
|
+
removeEmptyElements?: boolean | {
|
|
54
|
+
removeWithAttributes?: boolean;
|
|
55
|
+
};
|
|
56
|
+
removeRedundantAttributes?: boolean;
|
|
57
|
+
removeOptionalTags?: 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
|
+
};
|
|
74
|
+
sortAttributes?: boolean | 'alphabetical' | 'frequency';
|
|
75
|
+
sortAttributesWithLists?: boolean | 'alphabetical' | 'frequency';
|
|
76
|
+
}
|
|
77
|
+
type HtmlnanoModuleAttrsHandler = (attrs: Record<string, string | boolean | void>, node: PostHTML.Node) => Record<string, string | boolean | void>;
|
|
78
|
+
type HtmlnanoModuleContentHandler = (content: Array<PostHTMLNodeLike>, node: PostHTML.Node) => MaybeArray<PostHTMLNodeLike>;
|
|
79
|
+
type HtmlnanoModuleNodeHandler = (node: PostHTMLNodeLike) => PostHTML.Node | string;
|
|
80
|
+
type OptionalOptions<T> = T extends boolean | string | Function | number | null | undefined ? T : T extends object ? Partial<T> : T;
|
|
81
|
+
type HtmlnanoModule<Options = any> = {
|
|
82
|
+
onAttrs?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleAttrsHandler;
|
|
83
|
+
onContent?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleContentHandler;
|
|
84
|
+
onNode?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleNodeHandler;
|
|
85
|
+
default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
type RedundantWhitespaceMode = 'safe' | 'agressive' | false;
|
|
89
|
+
type MinifyAttributesOptions = {
|
|
90
|
+
metaContent?: boolean;
|
|
91
|
+
redundantWhitespaces?: RedundantWhitespaceMode | 'aggressive';
|
|
92
|
+
};
|
|
93
|
+
declare const mod: HtmlnanoModule<MinifyAttributesOptions>;
|
|
94
|
+
|
|
95
|
+
export { mod as default };
|