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
|
@@ -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
|
}
|
|
@@ -52,7 +85,8 @@ type HtmlnanoModule<Options = any> = {
|
|
|
52
85
|
default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
|
|
53
86
|
};
|
|
54
87
|
|
|
55
|
-
type
|
|
56
|
-
|
|
88
|
+
type SortAttributesOption = 'alphabetical' | 'frequency';
|
|
89
|
+
|
|
90
|
+
declare const mod: HtmlnanoModule<boolean | SortAttributesOption>;
|
|
57
91
|
|
|
58
92
|
export { mod as default };
|
|
@@ -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
|
}
|
|
@@ -52,7 +85,8 @@ type HtmlnanoModule<Options = any> = {
|
|
|
52
85
|
default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
|
|
53
86
|
};
|
|
54
87
|
|
|
55
|
-
type
|
|
56
|
-
|
|
88
|
+
type SortAttributesOption = 'alphabetical' | 'frequency';
|
|
89
|
+
|
|
90
|
+
declare const mod: HtmlnanoModule<boolean | SortAttributesOption>;
|
|
57
91
|
|
|
58
92
|
export { mod as default };
|
|
@@ -4,11 +4,12 @@ const validOptions = new Set([
|
|
|
4
4
|
'frequency',
|
|
5
5
|
'alphabetical'
|
|
6
6
|
]);
|
|
7
|
-
|
|
7
|
+
function resolveSortType(options) {
|
|
8
8
|
if (options === true) return 'alphabetical';
|
|
9
9
|
if (options === false) return false;
|
|
10
10
|
return validOptions.has(options) ? options : false;
|
|
11
|
-
}
|
|
11
|
+
}
|
|
12
|
+
|
|
12
13
|
class AttributeTokenChain {
|
|
13
14
|
addFromNodeAttrs(nodeAttrs) {
|
|
14
15
|
Object.keys(nodeAttrs).forEach((attrName)=>{
|
|
@@ -24,23 +25,41 @@ class AttributeTokenChain {
|
|
|
24
25
|
const _sortOrder = [
|
|
25
26
|
...this.freqData.entries()
|
|
26
27
|
];
|
|
27
|
-
_sortOrder.sort((a, b)=>
|
|
28
|
+
_sortOrder.sort((a, b)=>{
|
|
29
|
+
const freqDiff = b[1] - a[1];
|
|
30
|
+
if (freqDiff !== 0) return freqDiff;
|
|
31
|
+
return a[0].localeCompare(b[0]);
|
|
32
|
+
});
|
|
28
33
|
this.sortOrder = _sortOrder.map((i)=>i[0]);
|
|
29
34
|
}
|
|
30
35
|
sortFromNodeAttrs(nodeAttrs) {
|
|
31
36
|
const newAttrs = {};
|
|
32
|
-
// Convert node.attrs attrName into lower case.
|
|
37
|
+
// Convert node.attrs attrName into lower case while preserving originals.
|
|
33
38
|
const loweredNodeAttrs = {};
|
|
34
39
|
Object.entries(nodeAttrs).forEach(([attrName, attrValue])=>{
|
|
35
|
-
|
|
40
|
+
const attrNameLower = attrName.toLowerCase();
|
|
41
|
+
if (!loweredNodeAttrs[attrNameLower]) {
|
|
42
|
+
loweredNodeAttrs[attrNameLower] = {
|
|
43
|
+
name: attrName,
|
|
44
|
+
value: attrValue
|
|
45
|
+
};
|
|
46
|
+
}
|
|
36
47
|
});
|
|
37
48
|
if (!this.sortOrder) {
|
|
38
49
|
this.createSortOrder();
|
|
39
50
|
}
|
|
51
|
+
const seen = new Set();
|
|
40
52
|
this.sortOrder.forEach((attrNameLower)=>{
|
|
41
53
|
// The attrName inside "sortOrder" has been lowered
|
|
42
|
-
|
|
43
|
-
|
|
54
|
+
const originalAttr = loweredNodeAttrs[attrNameLower];
|
|
55
|
+
if (originalAttr != null) {
|
|
56
|
+
newAttrs[originalAttr.name] = originalAttr.value;
|
|
57
|
+
seen.add(attrNameLower);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
Object.entries(loweredNodeAttrs).forEach(([attrNameLower, originalAttr])=>{
|
|
61
|
+
if (!seen.has(attrNameLower)) {
|
|
62
|
+
newAttrs[originalAttr.name] = originalAttr.value;
|
|
44
63
|
}
|
|
45
64
|
});
|
|
46
65
|
return newAttrs;
|
|
@@ -52,7 +71,7 @@ class AttributeTokenChain {
|
|
|
52
71
|
}
|
|
53
72
|
/** Sort attibutes */ const mod = {
|
|
54
73
|
default (tree, options, moduleOptions) {
|
|
55
|
-
const sortType =
|
|
74
|
+
const sortType = resolveSortType(moduleOptions);
|
|
56
75
|
if (sortType === 'alphabetical') {
|
|
57
76
|
return sortAttributesInAlphabeticalOrder(tree);
|
|
58
77
|
}
|
|
@@ -2,11 +2,12 @@ const validOptions = new Set([
|
|
|
2
2
|
'frequency',
|
|
3
3
|
'alphabetical'
|
|
4
4
|
]);
|
|
5
|
-
|
|
5
|
+
function resolveSortType(options) {
|
|
6
6
|
if (options === true) return 'alphabetical';
|
|
7
7
|
if (options === false) return false;
|
|
8
8
|
return validOptions.has(options) ? options : false;
|
|
9
|
-
}
|
|
9
|
+
}
|
|
10
|
+
|
|
10
11
|
class AttributeTokenChain {
|
|
11
12
|
addFromNodeAttrs(nodeAttrs) {
|
|
12
13
|
Object.keys(nodeAttrs).forEach((attrName)=>{
|
|
@@ -22,23 +23,41 @@ class AttributeTokenChain {
|
|
|
22
23
|
const _sortOrder = [
|
|
23
24
|
...this.freqData.entries()
|
|
24
25
|
];
|
|
25
|
-
_sortOrder.sort((a, b)=>
|
|
26
|
+
_sortOrder.sort((a, b)=>{
|
|
27
|
+
const freqDiff = b[1] - a[1];
|
|
28
|
+
if (freqDiff !== 0) return freqDiff;
|
|
29
|
+
return a[0].localeCompare(b[0]);
|
|
30
|
+
});
|
|
26
31
|
this.sortOrder = _sortOrder.map((i)=>i[0]);
|
|
27
32
|
}
|
|
28
33
|
sortFromNodeAttrs(nodeAttrs) {
|
|
29
34
|
const newAttrs = {};
|
|
30
|
-
// Convert node.attrs attrName into lower case.
|
|
35
|
+
// Convert node.attrs attrName into lower case while preserving originals.
|
|
31
36
|
const loweredNodeAttrs = {};
|
|
32
37
|
Object.entries(nodeAttrs).forEach(([attrName, attrValue])=>{
|
|
33
|
-
|
|
38
|
+
const attrNameLower = attrName.toLowerCase();
|
|
39
|
+
if (!loweredNodeAttrs[attrNameLower]) {
|
|
40
|
+
loweredNodeAttrs[attrNameLower] = {
|
|
41
|
+
name: attrName,
|
|
42
|
+
value: attrValue
|
|
43
|
+
};
|
|
44
|
+
}
|
|
34
45
|
});
|
|
35
46
|
if (!this.sortOrder) {
|
|
36
47
|
this.createSortOrder();
|
|
37
48
|
}
|
|
49
|
+
const seen = new Set();
|
|
38
50
|
this.sortOrder.forEach((attrNameLower)=>{
|
|
39
51
|
// The attrName inside "sortOrder" has been lowered
|
|
40
|
-
|
|
41
|
-
|
|
52
|
+
const originalAttr = loweredNodeAttrs[attrNameLower];
|
|
53
|
+
if (originalAttr != null) {
|
|
54
|
+
newAttrs[originalAttr.name] = originalAttr.value;
|
|
55
|
+
seen.add(attrNameLower);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
Object.entries(loweredNodeAttrs).forEach(([attrNameLower, originalAttr])=>{
|
|
59
|
+
if (!seen.has(attrNameLower)) {
|
|
60
|
+
newAttrs[originalAttr.name] = originalAttr.value;
|
|
42
61
|
}
|
|
43
62
|
});
|
|
44
63
|
return newAttrs;
|
|
@@ -50,7 +69,7 @@ class AttributeTokenChain {
|
|
|
50
69
|
}
|
|
51
70
|
/** Sort attibutes */ const mod = {
|
|
52
71
|
default (tree, options, moduleOptions) {
|
|
53
|
-
const sortType =
|
|
72
|
+
const sortType = resolveSortType(moduleOptions);
|
|
54
73
|
if (sortType === 'alphabetical') {
|
|
55
74
|
return sortAttributesInAlphabeticalOrder(tree);
|
|
56
75
|
}
|
|
@@ -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
|
}
|
|
@@ -52,6 +85,8 @@ type HtmlnanoModule<Options = any> = {
|
|
|
52
85
|
default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
|
|
53
86
|
};
|
|
54
87
|
|
|
55
|
-
|
|
88
|
+
type SortAttributesOption = 'alphabetical' | 'frequency';
|
|
89
|
+
|
|
90
|
+
declare const mod: HtmlnanoModule<boolean | SortAttributesOption>;
|
|
56
91
|
|
|
57
92
|
export { mod as default };
|
|
@@ -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
|
}
|
|
@@ -52,6 +85,8 @@ type HtmlnanoModule<Options = any> = {
|
|
|
52
85
|
default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
|
|
53
86
|
};
|
|
54
87
|
|
|
55
|
-
|
|
88
|
+
type SortAttributesOption = 'alphabetical' | 'frequency';
|
|
89
|
+
|
|
90
|
+
declare const mod: HtmlnanoModule<boolean | SortAttributesOption>;
|
|
56
91
|
|
|
57
92
|
export { mod as default };
|
|
@@ -2,53 +2,69 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
2
2
|
|
|
3
3
|
var collapseAttributeWhitespace_js = require('./collapseAttributeWhitespace.js');
|
|
4
4
|
|
|
5
|
-
// class, rel, ping
|
|
6
5
|
const validOptions = new Set([
|
|
7
6
|
'frequency',
|
|
8
7
|
'alphabetical'
|
|
9
8
|
]);
|
|
10
|
-
|
|
9
|
+
function resolveSortType(options) {
|
|
11
10
|
if (options === true) return 'alphabetical';
|
|
12
11
|
if (options === false) return false;
|
|
13
12
|
return validOptions.has(options) ? options : false;
|
|
14
|
-
}
|
|
15
|
-
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// class, rel, ping
|
|
16
|
+
class ListAttributeTokenChain {
|
|
16
17
|
addFromNodeAttrsArray(attrValuesArray) {
|
|
17
18
|
attrValuesArray.forEach((attrValue)=>{
|
|
18
|
-
if (
|
|
19
|
-
|
|
19
|
+
if (!attrValue) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (this.tokenCounts.has(attrValue)) {
|
|
23
|
+
this.tokenCounts.set(attrValue, this.tokenCounts.get(attrValue) + 1);
|
|
20
24
|
} else {
|
|
21
|
-
this.
|
|
25
|
+
this.tokenCounts.set(attrValue, 1);
|
|
22
26
|
}
|
|
23
27
|
});
|
|
24
28
|
}
|
|
25
29
|
createSortOrder() {
|
|
26
|
-
const
|
|
27
|
-
...this.
|
|
30
|
+
const nextSortOrder = [
|
|
31
|
+
...this.tokenCounts.entries()
|
|
28
32
|
];
|
|
29
|
-
|
|
30
|
-
this.
|
|
33
|
+
nextSortOrder.sort((a, b)=>b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
34
|
+
this.sortedTokens = nextSortOrder.map((i)=>i[0]);
|
|
31
35
|
}
|
|
32
36
|
sortFromNodeAttrsArray(attrValuesArray) {
|
|
33
37
|
const resultArray = [];
|
|
34
|
-
|
|
38
|
+
const tokenCounts = new Map();
|
|
39
|
+
attrValuesArray.forEach((attrValue)=>{
|
|
40
|
+
var _tokenCounts_get;
|
|
41
|
+
if (!attrValue) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
tokenCounts.set(attrValue, ((_tokenCounts_get = tokenCounts.get(attrValue)) != null ? _tokenCounts_get : 0) + 1);
|
|
45
|
+
});
|
|
46
|
+
if (!this.sortedTokens) {
|
|
35
47
|
this.createSortOrder();
|
|
36
48
|
}
|
|
37
|
-
this.
|
|
38
|
-
|
|
49
|
+
this.sortedTokens.forEach((k)=>{
|
|
50
|
+
const count = tokenCounts.get(k);
|
|
51
|
+
if (!count) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
for(let i = 0; i < count; i += 1){
|
|
39
55
|
resultArray.push(k);
|
|
40
56
|
}
|
|
41
57
|
});
|
|
42
58
|
return resultArray;
|
|
43
59
|
}
|
|
44
60
|
constructor(){
|
|
45
|
-
/** <attr, frequency> */ this.
|
|
46
|
-
this.
|
|
61
|
+
/** <attr, frequency> */ this.tokenCounts = new Map();
|
|
62
|
+
this.sortedTokens = null;
|
|
47
63
|
}
|
|
48
64
|
}
|
|
49
65
|
/** Sort values inside list-like attributes (e.g. class, rel) */ const mod = {
|
|
50
66
|
default (tree, options, moduleOptions) {
|
|
51
|
-
const sortType =
|
|
67
|
+
const sortType = resolveSortType(moduleOptions);
|
|
52
68
|
if (sortType === 'alphabetical') {
|
|
53
69
|
return sortAttributesWithListsInAlphabeticalOrder(tree);
|
|
54
70
|
}
|
|
@@ -59,58 +75,51 @@ class AttributeTokenChain {
|
|
|
59
75
|
return tree;
|
|
60
76
|
}
|
|
61
77
|
};
|
|
62
|
-
|
|
78
|
+
const splitListAttributeValues = (attrValue)=>attrValue.split(/\s+/).filter(Boolean);
|
|
79
|
+
function walkListAttributes(tree, walkFn) {
|
|
63
80
|
tree.walk((node)=>{
|
|
64
81
|
if (!node.attrs) {
|
|
65
82
|
return node;
|
|
66
83
|
}
|
|
67
|
-
|
|
84
|
+
const tagName = node.tag ? node.tag.toLowerCase() : undefined;
|
|
85
|
+
Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
|
|
68
86
|
const attrNameLower = attrName.toLowerCase();
|
|
69
|
-
if (!collapseAttributeWhitespace_js.
|
|
87
|
+
if (!collapseAttributeWhitespace_js.isListAttribute(attrNameLower, tagName) || typeof attrValues !== 'string') {
|
|
70
88
|
return;
|
|
71
89
|
}
|
|
72
|
-
|
|
73
|
-
node.attrs[attrName] = attrValues.sort((a, b)=>{
|
|
74
|
-
// @ts-expect-error -- deliberately use minus operator to sort things
|
|
75
|
-
return typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b;
|
|
76
|
-
}).join(' ');
|
|
90
|
+
walkFn(node.attrs, attrName, attrValues);
|
|
77
91
|
});
|
|
78
92
|
return node;
|
|
79
93
|
});
|
|
94
|
+
}
|
|
95
|
+
function sortAttributesWithListsInAlphabeticalOrder(tree) {
|
|
96
|
+
walkListAttributes(tree, (nodeAttrs, attrName, attrValues)=>{
|
|
97
|
+
const values = splitListAttributeValues(attrValues);
|
|
98
|
+
if (values.length < 2) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
nodeAttrs[attrName] = values.sort((a, b)=>{
|
|
102
|
+
// @ts-expect-error -- deliberately use minus operator to sort things
|
|
103
|
+
return typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b;
|
|
104
|
+
}).join(' ');
|
|
105
|
+
});
|
|
80
106
|
return tree;
|
|
81
107
|
}
|
|
82
108
|
function sortAttributesWithListsByFrequency(tree) {
|
|
83
|
-
const tokenChainObj = {};
|
|
109
|
+
const tokenChainObj = {};
|
|
84
110
|
// Traverse through tree to get frequency
|
|
85
|
-
tree
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
|
|
90
|
-
const attrNameLower = attrName.toLowerCase();
|
|
91
|
-
if (!collapseAttributeWhitespace_js.attributesWithLists.has(attrNameLower)) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
tokenChainObj[attrNameLower] = tokenChainObj[attrNameLower] || new AttributeTokenChain();
|
|
95
|
-
tokenChainObj[attrNameLower].addFromNodeAttrsArray(attrValues.split(/\s/));
|
|
96
|
-
});
|
|
97
|
-
return node;
|
|
111
|
+
walkListAttributes(tree, (_nodeAttrs, attrName, attrValues)=>{
|
|
112
|
+
const attrNameLower = attrName.toLowerCase();
|
|
113
|
+
tokenChainObj[attrNameLower] = tokenChainObj[attrNameLower] || new ListAttributeTokenChain();
|
|
114
|
+
tokenChainObj[attrNameLower].addFromNodeAttrsArray(splitListAttributeValues(attrValues));
|
|
98
115
|
});
|
|
99
116
|
// Traverse through tree again, this time sort the attribute values
|
|
100
|
-
tree
|
|
101
|
-
|
|
102
|
-
|
|
117
|
+
walkListAttributes(tree, (nodeAttrs, attrName, attrValues)=>{
|
|
118
|
+
const attrNameLower = attrName.toLowerCase();
|
|
119
|
+
if (!tokenChainObj[attrNameLower]) {
|
|
120
|
+
return;
|
|
103
121
|
}
|
|
104
|
-
|
|
105
|
-
const attrNameLower = attrName.toLowerCase();
|
|
106
|
-
if (!collapseAttributeWhitespace_js.attributesWithLists.has(attrNameLower)) {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
if (tokenChainObj[attrNameLower]) {
|
|
110
|
-
node.attrs[attrName] = tokenChainObj[attrNameLower].sortFromNodeAttrsArray(attrValues.split(/\s/)).join(' ');
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
return node;
|
|
122
|
+
nodeAttrs[attrName] = tokenChainObj[attrNameLower].sortFromNodeAttrsArray(splitListAttributeValues(attrValues)).join(' ');
|
|
114
123
|
});
|
|
115
124
|
return tree;
|
|
116
125
|
}
|