htmlnano 3.0.0 → 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 +18 -17
- 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 +99 -24
- package/dist/_modules/mergeScripts.mjs +99 -24
- package/dist/_modules/mergeStyles.d.mts +36 -3
- package/dist/_modules/mergeStyles.d.ts +36 -3
- package/dist/_modules/mergeStyles.js +56 -4
- package/dist/_modules/mergeStyles.mjs +56 -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 +94 -5
- package/dist/_modules/minifyJs.mjs +95 -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 +38 -13
- package/dist/_modules/removeUnusedCss.mjs +39 -14
- package/dist/_modules/sortAttributes.d.mts +36 -3
- package/dist/_modules/sortAttributes.d.ts +36 -3
- package/dist/_modules/sortAttributes.js +23 -5
- package/dist/_modules/sortAttributes.mjs +23 -5
- package/dist/_modules/sortAttributesWithLists.d.mts +36 -3
- package/dist/_modules/sortAttributesWithLists.d.ts +36 -3
- package/dist/_modules/sortAttributesWithLists.js +30 -8
- package/dist/_modules/sortAttributesWithLists.mjs +31 -9
- 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 +4 -0
- package/dist/presets/max.mjs +4 -0
- 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 +21 -13
|
@@ -2,19 +2,33 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
2
2
|
|
|
3
3
|
var collapseAttributeWhitespace_js = require('./collapseAttributeWhitespace.js');
|
|
4
4
|
|
|
5
|
+
const caseInsensitiveListAttributes = new Set([
|
|
6
|
+
'rel',
|
|
7
|
+
'sandbox',
|
|
8
|
+
'dropzone',
|
|
9
|
+
'sizes'
|
|
10
|
+
]);
|
|
11
|
+
function getDeduplicationKey(attrNameLower, attrValue) {
|
|
12
|
+
if (!caseInsensitiveListAttributes.has(attrNameLower)) {
|
|
13
|
+
return attrValue;
|
|
14
|
+
}
|
|
15
|
+
return attrValue.toLowerCase();
|
|
16
|
+
}
|
|
5
17
|
/** Deduplicate values inside list-like attributes (e.g. class, rel) */ const mod = {
|
|
6
18
|
onAttrs () {
|
|
7
|
-
return (attrs)=>{
|
|
19
|
+
return (attrs, node)=>{
|
|
8
20
|
const newAttrs = attrs;
|
|
21
|
+
const tagName = node.tag ? node.tag.toLowerCase() : undefined;
|
|
9
22
|
Object.keys(attrs).forEach((attrName)=>{
|
|
10
|
-
|
|
23
|
+
const attrNameLower = attrName.toLowerCase();
|
|
24
|
+
if (!collapseAttributeWhitespace_js.isListAttribute(attrNameLower, tagName)) {
|
|
11
25
|
return;
|
|
12
26
|
}
|
|
13
27
|
if (typeof attrs[attrName] !== 'string') {
|
|
14
28
|
return;
|
|
15
29
|
}
|
|
16
30
|
const attrValues = attrs[attrName].split(/\s/);
|
|
17
|
-
const
|
|
31
|
+
const uniqueAttrValues = new Set();
|
|
18
32
|
const deduplicatedAttrValues = [];
|
|
19
33
|
attrValues.forEach((attrValue)=>{
|
|
20
34
|
if (!attrValue) {
|
|
@@ -22,11 +36,12 @@ var collapseAttributeWhitespace_js = require('./collapseAttributeWhitespace.js')
|
|
|
22
36
|
deduplicatedAttrValues.push('');
|
|
23
37
|
return;
|
|
24
38
|
}
|
|
25
|
-
|
|
39
|
+
const deduplicationKey = getDeduplicationKey(attrNameLower, attrValue);
|
|
40
|
+
if (uniqueAttrValues.has(deduplicationKey)) {
|
|
26
41
|
return;
|
|
27
42
|
}
|
|
28
43
|
deduplicatedAttrValues.push(attrValue);
|
|
29
|
-
|
|
44
|
+
uniqueAttrValues.add(deduplicationKey);
|
|
30
45
|
});
|
|
31
46
|
newAttrs[attrName] = deduplicatedAttrValues.join(' ');
|
|
32
47
|
});
|
|
@@ -1,18 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isListAttribute } from './collapseAttributeWhitespace.mjs';
|
|
2
2
|
|
|
3
|
+
const caseInsensitiveListAttributes = new Set([
|
|
4
|
+
'rel',
|
|
5
|
+
'sandbox',
|
|
6
|
+
'dropzone',
|
|
7
|
+
'sizes'
|
|
8
|
+
]);
|
|
9
|
+
function getDeduplicationKey(attrNameLower, attrValue) {
|
|
10
|
+
if (!caseInsensitiveListAttributes.has(attrNameLower)) {
|
|
11
|
+
return attrValue;
|
|
12
|
+
}
|
|
13
|
+
return attrValue.toLowerCase();
|
|
14
|
+
}
|
|
3
15
|
/** Deduplicate values inside list-like attributes (e.g. class, rel) */ const mod = {
|
|
4
16
|
onAttrs () {
|
|
5
|
-
return (attrs)=>{
|
|
17
|
+
return (attrs, node)=>{
|
|
6
18
|
const newAttrs = attrs;
|
|
19
|
+
const tagName = node.tag ? node.tag.toLowerCase() : undefined;
|
|
7
20
|
Object.keys(attrs).forEach((attrName)=>{
|
|
8
|
-
|
|
21
|
+
const attrNameLower = attrName.toLowerCase();
|
|
22
|
+
if (!isListAttribute(attrNameLower, tagName)) {
|
|
9
23
|
return;
|
|
10
24
|
}
|
|
11
25
|
if (typeof attrs[attrName] !== 'string') {
|
|
12
26
|
return;
|
|
13
27
|
}
|
|
14
28
|
const attrValues = attrs[attrName].split(/\s/);
|
|
15
|
-
const
|
|
29
|
+
const uniqueAttrValues = new Set();
|
|
16
30
|
const deduplicatedAttrValues = [];
|
|
17
31
|
attrValues.forEach((attrValue)=>{
|
|
18
32
|
if (!attrValue) {
|
|
@@ -20,11 +34,12 @@ import { attributesWithLists } from './collapseAttributeWhitespace.mjs';
|
|
|
20
34
|
deduplicatedAttrValues.push('');
|
|
21
35
|
return;
|
|
22
36
|
}
|
|
23
|
-
|
|
37
|
+
const deduplicationKey = getDeduplicationKey(attrNameLower, attrValue);
|
|
38
|
+
if (uniqueAttrValues.has(deduplicationKey)) {
|
|
24
39
|
return;
|
|
25
40
|
}
|
|
26
41
|
deduplicatedAttrValues.push(attrValue);
|
|
27
|
-
|
|
42
|
+
uniqueAttrValues.add(deduplicationKey);
|
|
28
43
|
});
|
|
29
44
|
newAttrs[attrName] = deduplicatedAttrValues.join(' ');
|
|
30
45
|
});
|
|
@@ -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,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,50 +2,125 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
2
2
|
|
|
3
3
|
var helpers_js = require('../helpers.js');
|
|
4
4
|
|
|
5
|
+
function normalizeAsyncAttr(attrs) {
|
|
6
|
+
if (!attrs) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (attrs.async === '') {
|
|
10
|
+
attrs.async = true;
|
|
11
|
+
}
|
|
12
|
+
if (attrs.nomodule === '') {
|
|
13
|
+
attrs.nomodule = true;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function getScriptType(attrs) {
|
|
17
|
+
const type = attrs.type || 'text/javascript';
|
|
18
|
+
return typeof type === 'string' ? type.toLowerCase() : 'text/javascript';
|
|
19
|
+
}
|
|
20
|
+
function isMergeableScriptType(type) {
|
|
21
|
+
return type === 'text/javascript' || type === 'application/javascript';
|
|
22
|
+
}
|
|
23
|
+
const booleanAttrs = new Set([
|
|
24
|
+
'async',
|
|
25
|
+
'defer',
|
|
26
|
+
'nomodule'
|
|
27
|
+
]);
|
|
28
|
+
function normalizeScriptAttrsForKey(attrs, scriptType) {
|
|
29
|
+
const normalized = {
|
|
30
|
+
type: scriptType
|
|
31
|
+
};
|
|
32
|
+
for (const [key, value] of Object.entries(attrs)){
|
|
33
|
+
if (key === 'src' || key === 'integrity' || key === 'type') {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (value === undefined) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (booleanAttrs.has(key)) {
|
|
40
|
+
normalized[key] = true;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
normalized[key] = value;
|
|
44
|
+
}
|
|
45
|
+
return normalized;
|
|
46
|
+
}
|
|
47
|
+
function buildScriptKey(attrs, scriptType, scriptSrcIndex) {
|
|
48
|
+
const normalizedAttrs = normalizeScriptAttrsForKey(attrs, scriptType);
|
|
49
|
+
const keyObject = {
|
|
50
|
+
index: scriptSrcIndex,
|
|
51
|
+
...normalizedAttrs
|
|
52
|
+
};
|
|
53
|
+
const sortedKeys = Object.keys(keyObject).sort();
|
|
54
|
+
const sortedKeyObject = {};
|
|
55
|
+
for (const key of sortedKeys){
|
|
56
|
+
sortedKeyObject[key] = keyObject[key];
|
|
57
|
+
}
|
|
58
|
+
return JSON.stringify(sortedKeyObject);
|
|
59
|
+
}
|
|
60
|
+
function endsWithLineComment(scriptContent) {
|
|
61
|
+
const lastNewlineIndex = Math.max(scriptContent.lastIndexOf('\n'), scriptContent.lastIndexOf('\r'));
|
|
62
|
+
const lastLine = lastNewlineIndex === -1 ? scriptContent : scriptContent.slice(lastNewlineIndex + 1);
|
|
63
|
+
return /\/\/.*$/.test(lastLine);
|
|
64
|
+
}
|
|
65
|
+
function mergeScriptNodes(scriptNodesIndex, tracking) {
|
|
66
|
+
for (const scriptNodes of Object.values(scriptNodesIndex)){
|
|
67
|
+
if (scriptNodes.length < 2) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const lastScriptNode = scriptNodes.pop();
|
|
71
|
+
tracking.mergedScriptNodes.add(lastScriptNode);
|
|
72
|
+
scriptNodes.reverse().forEach((scriptNode)=>{
|
|
73
|
+
let scriptContent = helpers_js.extractTextContentFromNode(scriptNode).trim();
|
|
74
|
+
if (!scriptContent) {
|
|
75
|
+
tracking.removedScriptNodes.add(scriptNode);
|
|
76
|
+
// @ts-expect-error -- remove node
|
|
77
|
+
scriptNode.tag = false;
|
|
78
|
+
scriptNode.content = [];
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (endsWithLineComment(scriptContent)) {
|
|
82
|
+
scriptContent += '\n;';
|
|
83
|
+
} else if (scriptContent.slice(-1) !== ';') {
|
|
84
|
+
scriptContent += ';';
|
|
85
|
+
}
|
|
86
|
+
lastScriptNode.content = lastScriptNode.content || [];
|
|
87
|
+
lastScriptNode.content.unshift(scriptContent);
|
|
88
|
+
tracking.removedScriptNodes.add(scriptNode);
|
|
89
|
+
// @ts-expect-error -- remove node
|
|
90
|
+
scriptNode.tag = false;
|
|
91
|
+
scriptNode.content = [];
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
5
95
|
/* Merge multiple <script> into one */ const mod = {
|
|
6
96
|
default (tree) {
|
|
7
97
|
const scriptNodesIndex = {};
|
|
98
|
+
const tracking = {
|
|
99
|
+
mergedScriptNodes: new WeakSet(),
|
|
100
|
+
removedScriptNodes: new WeakSet()
|
|
101
|
+
};
|
|
8
102
|
let scriptSrcIndex = 1;
|
|
9
103
|
tree.match({
|
|
10
104
|
tag: 'script'
|
|
11
105
|
}, (node)=>{
|
|
12
106
|
const nodeAttrs = node.attrs || {};
|
|
107
|
+
normalizeAsyncAttr(nodeAttrs);
|
|
13
108
|
if ('src' in nodeAttrs || 'integrity' in nodeAttrs) {
|
|
14
109
|
scriptSrcIndex++;
|
|
15
110
|
return node;
|
|
16
111
|
}
|
|
17
|
-
const scriptType = nodeAttrs
|
|
18
|
-
if (scriptType
|
|
112
|
+
const scriptType = getScriptType(nodeAttrs);
|
|
113
|
+
if (!isMergeableScriptType(scriptType)) {
|
|
19
114
|
return node;
|
|
20
115
|
}
|
|
21
|
-
const scriptKey =
|
|
22
|
-
id: nodeAttrs.id,
|
|
23
|
-
class: nodeAttrs.class,
|
|
24
|
-
type: scriptType,
|
|
25
|
-
defer: nodeAttrs.defer !== undefined,
|
|
26
|
-
async: nodeAttrs.async !== undefined,
|
|
27
|
-
index: scriptSrcIndex
|
|
28
|
-
});
|
|
116
|
+
const scriptKey = buildScriptKey(nodeAttrs, scriptType, scriptSrcIndex);
|
|
29
117
|
if (!scriptNodesIndex[scriptKey]) {
|
|
30
118
|
scriptNodesIndex[scriptKey] = [];
|
|
31
119
|
}
|
|
32
120
|
scriptNodesIndex[scriptKey].push(node);
|
|
33
121
|
return node;
|
|
34
122
|
});
|
|
35
|
-
|
|
36
|
-
const lastScriptNode = scriptNodes.pop();
|
|
37
|
-
scriptNodes.reverse().forEach((scriptNode)=>{
|
|
38
|
-
let scriptContent = helpers_js.extractTextContentFromNode(scriptNode).trim();
|
|
39
|
-
if (scriptContent.slice(-1) !== ';') {
|
|
40
|
-
scriptContent += ';';
|
|
41
|
-
}
|
|
42
|
-
lastScriptNode.content = lastScriptNode.content || [];
|
|
43
|
-
lastScriptNode.content.unshift(scriptContent);
|
|
44
|
-
// @ts-expect-error -- remove node
|
|
45
|
-
scriptNode.tag = false;
|
|
46
|
-
scriptNode.content = [];
|
|
47
|
-
});
|
|
48
|
-
}
|
|
123
|
+
mergeScriptNodes(scriptNodesIndex, tracking);
|
|
49
124
|
return tree;
|
|
50
125
|
}
|
|
51
126
|
};
|
|
@@ -1,49 +1,124 @@
|
|
|
1
1
|
import { extractTextContentFromNode } from '../helpers.mjs';
|
|
2
2
|
|
|
3
|
+
function normalizeAsyncAttr(attrs) {
|
|
4
|
+
if (!attrs) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
if (attrs.async === '') {
|
|
8
|
+
attrs.async = true;
|
|
9
|
+
}
|
|
10
|
+
if (attrs.nomodule === '') {
|
|
11
|
+
attrs.nomodule = true;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function getScriptType(attrs) {
|
|
15
|
+
const type = attrs.type || 'text/javascript';
|
|
16
|
+
return typeof type === 'string' ? type.toLowerCase() : 'text/javascript';
|
|
17
|
+
}
|
|
18
|
+
function isMergeableScriptType(type) {
|
|
19
|
+
return type === 'text/javascript' || type === 'application/javascript';
|
|
20
|
+
}
|
|
21
|
+
const booleanAttrs = new Set([
|
|
22
|
+
'async',
|
|
23
|
+
'defer',
|
|
24
|
+
'nomodule'
|
|
25
|
+
]);
|
|
26
|
+
function normalizeScriptAttrsForKey(attrs, scriptType) {
|
|
27
|
+
const normalized = {
|
|
28
|
+
type: scriptType
|
|
29
|
+
};
|
|
30
|
+
for (const [key, value] of Object.entries(attrs)){
|
|
31
|
+
if (key === 'src' || key === 'integrity' || key === 'type') {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (value === undefined) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (booleanAttrs.has(key)) {
|
|
38
|
+
normalized[key] = true;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
normalized[key] = value;
|
|
42
|
+
}
|
|
43
|
+
return normalized;
|
|
44
|
+
}
|
|
45
|
+
function buildScriptKey(attrs, scriptType, scriptSrcIndex) {
|
|
46
|
+
const normalizedAttrs = normalizeScriptAttrsForKey(attrs, scriptType);
|
|
47
|
+
const keyObject = {
|
|
48
|
+
index: scriptSrcIndex,
|
|
49
|
+
...normalizedAttrs
|
|
50
|
+
};
|
|
51
|
+
const sortedKeys = Object.keys(keyObject).sort();
|
|
52
|
+
const sortedKeyObject = {};
|
|
53
|
+
for (const key of sortedKeys){
|
|
54
|
+
sortedKeyObject[key] = keyObject[key];
|
|
55
|
+
}
|
|
56
|
+
return JSON.stringify(sortedKeyObject);
|
|
57
|
+
}
|
|
58
|
+
function endsWithLineComment(scriptContent) {
|
|
59
|
+
const lastNewlineIndex = Math.max(scriptContent.lastIndexOf('\n'), scriptContent.lastIndexOf('\r'));
|
|
60
|
+
const lastLine = lastNewlineIndex === -1 ? scriptContent : scriptContent.slice(lastNewlineIndex + 1);
|
|
61
|
+
return /\/\/.*$/.test(lastLine);
|
|
62
|
+
}
|
|
63
|
+
function mergeScriptNodes(scriptNodesIndex, tracking) {
|
|
64
|
+
for (const scriptNodes of Object.values(scriptNodesIndex)){
|
|
65
|
+
if (scriptNodes.length < 2) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const lastScriptNode = scriptNodes.pop();
|
|
69
|
+
tracking.mergedScriptNodes.add(lastScriptNode);
|
|
70
|
+
scriptNodes.reverse().forEach((scriptNode)=>{
|
|
71
|
+
let scriptContent = extractTextContentFromNode(scriptNode).trim();
|
|
72
|
+
if (!scriptContent) {
|
|
73
|
+
tracking.removedScriptNodes.add(scriptNode);
|
|
74
|
+
// @ts-expect-error -- remove node
|
|
75
|
+
scriptNode.tag = false;
|
|
76
|
+
scriptNode.content = [];
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (endsWithLineComment(scriptContent)) {
|
|
80
|
+
scriptContent += '\n;';
|
|
81
|
+
} else if (scriptContent.slice(-1) !== ';') {
|
|
82
|
+
scriptContent += ';';
|
|
83
|
+
}
|
|
84
|
+
lastScriptNode.content = lastScriptNode.content || [];
|
|
85
|
+
lastScriptNode.content.unshift(scriptContent);
|
|
86
|
+
tracking.removedScriptNodes.add(scriptNode);
|
|
87
|
+
// @ts-expect-error -- remove node
|
|
88
|
+
scriptNode.tag = false;
|
|
89
|
+
scriptNode.content = [];
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
3
93
|
/* Merge multiple <script> into one */ const mod = {
|
|
4
94
|
default (tree) {
|
|
5
95
|
const scriptNodesIndex = {};
|
|
96
|
+
const tracking = {
|
|
97
|
+
mergedScriptNodes: new WeakSet(),
|
|
98
|
+
removedScriptNodes: new WeakSet()
|
|
99
|
+
};
|
|
6
100
|
let scriptSrcIndex = 1;
|
|
7
101
|
tree.match({
|
|
8
102
|
tag: 'script'
|
|
9
103
|
}, (node)=>{
|
|
10
104
|
const nodeAttrs = node.attrs || {};
|
|
105
|
+
normalizeAsyncAttr(nodeAttrs);
|
|
11
106
|
if ('src' in nodeAttrs || 'integrity' in nodeAttrs) {
|
|
12
107
|
scriptSrcIndex++;
|
|
13
108
|
return node;
|
|
14
109
|
}
|
|
15
|
-
const scriptType = nodeAttrs
|
|
16
|
-
if (scriptType
|
|
110
|
+
const scriptType = getScriptType(nodeAttrs);
|
|
111
|
+
if (!isMergeableScriptType(scriptType)) {
|
|
17
112
|
return node;
|
|
18
113
|
}
|
|
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
|
-
});
|
|
114
|
+
const scriptKey = buildScriptKey(nodeAttrs, scriptType, scriptSrcIndex);
|
|
27
115
|
if (!scriptNodesIndex[scriptKey]) {
|
|
28
116
|
scriptNodesIndex[scriptKey] = [];
|
|
29
117
|
}
|
|
30
118
|
scriptNodesIndex[scriptKey].push(node);
|
|
31
119
|
return node;
|
|
32
120
|
});
|
|
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
|
-
}
|
|
121
|
+
mergeScriptNodes(scriptNodesIndex, tracking);
|
|
47
122
|
return tree;
|
|
48
123
|
}
|
|
49
124
|
};
|