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,5 +1,29 @@
|
|
|
1
1
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
2
|
|
|
3
|
+
function normalizeAttrValue(value) {
|
|
4
|
+
if (typeof value !== 'string') return null;
|
|
5
|
+
const trimmed = value.trim();
|
|
6
|
+
return trimmed.length === 0 ? '' : trimmed.toLowerCase();
|
|
7
|
+
}
|
|
8
|
+
function findAttrEntry(attrs, name) {
|
|
9
|
+
const targetName = name.toLowerCase();
|
|
10
|
+
for (const [attrName, attrValue] of Object.entries(attrs)){
|
|
11
|
+
if (attrName.toLowerCase() === targetName) {
|
|
12
|
+
return {
|
|
13
|
+
name: attrName,
|
|
14
|
+
value: attrValue
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function getNormalizedAttrValue(attrs, name) {
|
|
21
|
+
const entry = findAttrEntry(attrs, name);
|
|
22
|
+
return entry ? normalizeAttrValue(entry.value) : null;
|
|
23
|
+
}
|
|
24
|
+
function hasAttr(attrs, name) {
|
|
25
|
+
return findAttrEntry(attrs, name) !== null;
|
|
26
|
+
}
|
|
3
27
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#JavaScript_types
|
|
4
28
|
const redundantScriptTypes = new Set([
|
|
5
29
|
'application/javascript',
|
|
@@ -34,22 +58,14 @@ const missingValueDefaultAttributes = {
|
|
|
34
58
|
script: {
|
|
35
59
|
language: 'javascript',
|
|
36
60
|
type: (attrs)=>{
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
if (typeof attrValue === 'string') {
|
|
42
|
-
return redundantScriptTypes.has(attrValue);
|
|
43
|
-
}
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
return false;
|
|
61
|
+
const typeValue = getNormalizedAttrValue(attrs, 'type');
|
|
62
|
+
return typeValue !== null && redundantScriptTypes.has(typeValue);
|
|
47
63
|
},
|
|
48
64
|
// Remove attribute if the function returns false
|
|
49
65
|
charset: (attrs)=>{
|
|
50
66
|
// The charset attribute only really makes sense on “external” SCRIPT elements:
|
|
51
67
|
// http://perfectionkills.com/optimizing-html/#8_script_charset
|
|
52
|
-
return !attrs
|
|
68
|
+
return !hasAttr(attrs, 'src');
|
|
53
69
|
}
|
|
54
70
|
},
|
|
55
71
|
style: {
|
|
@@ -60,19 +76,15 @@ const missingValueDefaultAttributes = {
|
|
|
60
76
|
media: 'all',
|
|
61
77
|
type: (attrs)=>{
|
|
62
78
|
// https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
if (attrName.toLowerCase() === 'rel' && attrValue === 'stylesheet') {
|
|
68
|
-
isRelStyleSheet = true;
|
|
69
|
-
}
|
|
70
|
-
if (attrName.toLowerCase() === 'type' && attrValue === 'text/css') {
|
|
71
|
-
isTypeTextCSS = true;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
79
|
+
const relValue = getNormalizedAttrValue(attrs, 'rel');
|
|
80
|
+
const typeValue = getNormalizedAttrValue(attrs, 'type');
|
|
81
|
+
if (!relValue || !typeValue) {
|
|
82
|
+
return false;
|
|
74
83
|
}
|
|
75
|
-
|
|
84
|
+
const relTokens = relValue.split(/\s+/);
|
|
85
|
+
const isRelStyleSheet = relTokens.includes('stylesheet');
|
|
86
|
+
const isTypeTextCSS = typeValue === 'text/css';
|
|
87
|
+
// Only "text/css" is redundant for link[rel=stylesheet]. Otherwise "type" shouldn't be removed
|
|
76
88
|
return isRelStyleSheet && isTypeTextCSS;
|
|
77
89
|
}
|
|
78
90
|
},
|
|
@@ -104,18 +116,21 @@ const tagsHaveMissingValueDefaultAttributes = new Set(Object.keys(missingValueDe
|
|
|
104
116
|
return (attrs, node)=>{
|
|
105
117
|
if (!node.tag) return attrs;
|
|
106
118
|
const newAttrs = attrs;
|
|
119
|
+
const attrsRecord = attrs;
|
|
107
120
|
if (tagsHaveMissingValueDefaultAttributes.has(node.tag)) {
|
|
108
121
|
const tagRedundantAttributes = missingValueDefaultAttributes[node.tag];
|
|
109
122
|
for (const redundantAttributeName of Object.keys(tagRedundantAttributes)){
|
|
110
123
|
const tagRedundantAttributeValue = tagRedundantAttributes[redundantAttributeName];
|
|
111
124
|
let isRemove = false;
|
|
125
|
+
const attrEntry = findAttrEntry(attrsRecord, redundantAttributeName);
|
|
112
126
|
if (typeof tagRedundantAttributeValue === 'function') {
|
|
113
|
-
isRemove = tagRedundantAttributeValue(
|
|
114
|
-
} else if (
|
|
115
|
-
|
|
127
|
+
isRemove = tagRedundantAttributeValue(attrsRecord);
|
|
128
|
+
} else if (attrEntry) {
|
|
129
|
+
const normalizedValue = normalizeAttrValue(attrEntry.value);
|
|
130
|
+
isRemove = normalizedValue !== null && normalizedValue === tagRedundantAttributeValue;
|
|
116
131
|
}
|
|
117
|
-
if (isRemove) {
|
|
118
|
-
delete newAttrs[
|
|
132
|
+
if (isRemove && attrEntry) {
|
|
133
|
+
delete newAttrs[attrEntry.name];
|
|
119
134
|
}
|
|
120
135
|
}
|
|
121
136
|
}
|
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
function normalizeAttrValue(value) {
|
|
2
|
+
if (typeof value !== 'string') return null;
|
|
3
|
+
const trimmed = value.trim();
|
|
4
|
+
return trimmed.length === 0 ? '' : trimmed.toLowerCase();
|
|
5
|
+
}
|
|
6
|
+
function findAttrEntry(attrs, name) {
|
|
7
|
+
const targetName = name.toLowerCase();
|
|
8
|
+
for (const [attrName, attrValue] of Object.entries(attrs)){
|
|
9
|
+
if (attrName.toLowerCase() === targetName) {
|
|
10
|
+
return {
|
|
11
|
+
name: attrName,
|
|
12
|
+
value: attrValue
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
function getNormalizedAttrValue(attrs, name) {
|
|
19
|
+
const entry = findAttrEntry(attrs, name);
|
|
20
|
+
return entry ? normalizeAttrValue(entry.value) : null;
|
|
21
|
+
}
|
|
22
|
+
function hasAttr(attrs, name) {
|
|
23
|
+
return findAttrEntry(attrs, name) !== null;
|
|
24
|
+
}
|
|
1
25
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#JavaScript_types
|
|
2
26
|
const redundantScriptTypes = new Set([
|
|
3
27
|
'application/javascript',
|
|
@@ -32,22 +56,14 @@ const missingValueDefaultAttributes = {
|
|
|
32
56
|
script: {
|
|
33
57
|
language: 'javascript',
|
|
34
58
|
type: (attrs)=>{
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (typeof attrValue === 'string') {
|
|
40
|
-
return redundantScriptTypes.has(attrValue);
|
|
41
|
-
}
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
return false;
|
|
59
|
+
const typeValue = getNormalizedAttrValue(attrs, 'type');
|
|
60
|
+
return typeValue !== null && redundantScriptTypes.has(typeValue);
|
|
45
61
|
},
|
|
46
62
|
// Remove attribute if the function returns false
|
|
47
63
|
charset: (attrs)=>{
|
|
48
64
|
// The charset attribute only really makes sense on “external” SCRIPT elements:
|
|
49
65
|
// http://perfectionkills.com/optimizing-html/#8_script_charset
|
|
50
|
-
return !attrs
|
|
66
|
+
return !hasAttr(attrs, 'src');
|
|
51
67
|
}
|
|
52
68
|
},
|
|
53
69
|
style: {
|
|
@@ -58,19 +74,15 @@ const missingValueDefaultAttributes = {
|
|
|
58
74
|
media: 'all',
|
|
59
75
|
type: (attrs)=>{
|
|
60
76
|
// https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
if (attrName.toLowerCase() === 'rel' && attrValue === 'stylesheet') {
|
|
66
|
-
isRelStyleSheet = true;
|
|
67
|
-
}
|
|
68
|
-
if (attrName.toLowerCase() === 'type' && attrValue === 'text/css') {
|
|
69
|
-
isTypeTextCSS = true;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
77
|
+
const relValue = getNormalizedAttrValue(attrs, 'rel');
|
|
78
|
+
const typeValue = getNormalizedAttrValue(attrs, 'type');
|
|
79
|
+
if (!relValue || !typeValue) {
|
|
80
|
+
return false;
|
|
72
81
|
}
|
|
73
|
-
|
|
82
|
+
const relTokens = relValue.split(/\s+/);
|
|
83
|
+
const isRelStyleSheet = relTokens.includes('stylesheet');
|
|
84
|
+
const isTypeTextCSS = typeValue === 'text/css';
|
|
85
|
+
// Only "text/css" is redundant for link[rel=stylesheet]. Otherwise "type" shouldn't be removed
|
|
74
86
|
return isRelStyleSheet && isTypeTextCSS;
|
|
75
87
|
}
|
|
76
88
|
},
|
|
@@ -102,18 +114,21 @@ const tagsHaveMissingValueDefaultAttributes = new Set(Object.keys(missingValueDe
|
|
|
102
114
|
return (attrs, node)=>{
|
|
103
115
|
if (!node.tag) return attrs;
|
|
104
116
|
const newAttrs = attrs;
|
|
117
|
+
const attrsRecord = attrs;
|
|
105
118
|
if (tagsHaveMissingValueDefaultAttributes.has(node.tag)) {
|
|
106
119
|
const tagRedundantAttributes = missingValueDefaultAttributes[node.tag];
|
|
107
120
|
for (const redundantAttributeName of Object.keys(tagRedundantAttributes)){
|
|
108
121
|
const tagRedundantAttributeValue = tagRedundantAttributes[redundantAttributeName];
|
|
109
122
|
let isRemove = false;
|
|
123
|
+
const attrEntry = findAttrEntry(attrsRecord, redundantAttributeName);
|
|
110
124
|
if (typeof tagRedundantAttributeValue === 'function') {
|
|
111
|
-
isRemove = tagRedundantAttributeValue(
|
|
112
|
-
} else if (
|
|
113
|
-
|
|
125
|
+
isRemove = tagRedundantAttributeValue(attrsRecord);
|
|
126
|
+
} else if (attrEntry) {
|
|
127
|
+
const normalizedValue = normalizeAttrValue(attrEntry.value);
|
|
128
|
+
isRemove = normalizedValue !== null && normalizedValue === tagRedundantAttributeValue;
|
|
114
129
|
}
|
|
115
|
-
if (isRemove) {
|
|
116
|
-
delete newAttrs[
|
|
130
|
+
if (isRemove && attrEntry) {
|
|
131
|
+
delete newAttrs[attrEntry.name];
|
|
117
132
|
}
|
|
118
133
|
}
|
|
119
134
|
}
|
|
@@ -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
|
}
|
|
@@ -54,6 +87,7 @@ type HtmlnanoModule<Options = any> = {
|
|
|
54
87
|
|
|
55
88
|
interface RemoveUnusedCssOptions {
|
|
56
89
|
tool?: 'purgeCSS' | 'uncss';
|
|
90
|
+
[key: string]: unknown;
|
|
57
91
|
}
|
|
58
92
|
declare const mod: HtmlnanoModule<RemoveUnusedCssOptions>;
|
|
59
93
|
|
|
@@ -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
|
}
|
|
@@ -54,6 +87,7 @@ type HtmlnanoModule<Options = any> = {
|
|
|
54
87
|
|
|
55
88
|
interface RemoveUnusedCssOptions {
|
|
56
89
|
tool?: 'purgeCSS' | 'uncss';
|
|
90
|
+
[key: string]: unknown;
|
|
57
91
|
}
|
|
58
92
|
declare const mod: HtmlnanoModule<RemoveUnusedCssOptions>;
|
|
59
93
|
|
|
@@ -9,10 +9,12 @@ const uncssOptions = {
|
|
|
9
9
|
],
|
|
10
10
|
stylesheets: []
|
|
11
11
|
};
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- uncss has no types
|
|
12
13
|
function processStyleNodeUnCSS(html, styleNode, uncssOptions, uncss) {
|
|
13
14
|
const css = helpers_js.extractCssFromStyleNode(styleNode);
|
|
15
|
+
const { strippedCss, isCdataWrapped } = helpers_js.stripCssCdata(css);
|
|
14
16
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- uncss no types
|
|
15
|
-
return runUncss(html,
|
|
17
|
+
return runUncss(html, strippedCss, uncssOptions, uncss).then((css)=>{
|
|
16
18
|
// uncss may have left some style tags empty
|
|
17
19
|
if (css.trim().length === 0) {
|
|
18
20
|
// @ts-expect-error -- explicitly remove the tag
|
|
@@ -21,10 +23,11 @@ function processStyleNodeUnCSS(html, styleNode, uncssOptions, uncss) {
|
|
|
21
23
|
return;
|
|
22
24
|
}
|
|
23
25
|
styleNode.content = [
|
|
24
|
-
css
|
|
26
|
+
helpers_js.wrapCssCdata(css, isCdataWrapped)
|
|
25
27
|
];
|
|
26
28
|
});
|
|
27
29
|
}
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- uncss callback uses untyped args
|
|
28
31
|
function runUncss(html, css, userOptions, uncss) {
|
|
29
32
|
if (typeof userOptions !== 'object') {
|
|
30
33
|
userOptions = {};
|
|
@@ -50,8 +53,8 @@ const purgeFromHtml = function(tree) {
|
|
|
50
53
|
// making the process faster
|
|
51
54
|
const selectors = [];
|
|
52
55
|
tree.walk((node)=>{
|
|
53
|
-
const classes = node.attrs && node.attrs.class
|
|
54
|
-
const ids = node.attrs && node.attrs.id
|
|
56
|
+
const classes = getSelectorTokens(node.attrs && node.attrs.class);
|
|
57
|
+
const ids = getSelectorTokens(node.attrs && node.attrs.id);
|
|
55
58
|
selectors.push(...classes, ...ids);
|
|
56
59
|
if (node.tag) {
|
|
57
60
|
selectors.push(node.tag);
|
|
@@ -60,9 +63,10 @@ const purgeFromHtml = function(tree) {
|
|
|
60
63
|
});
|
|
61
64
|
return ()=>selectors;
|
|
62
65
|
};
|
|
63
|
-
function processStyleNodePurgeCSS(tree, styleNode, purgecssOptions, purgecss) {
|
|
66
|
+
function processStyleNodePurgeCSS(tree, styleNode, purgecssOptions, purgecss, extractor) {
|
|
64
67
|
const css = helpers_js.extractCssFromStyleNode(styleNode);
|
|
65
|
-
|
|
68
|
+
const { strippedCss, isCdataWrapped } = helpers_js.stripCssCdata(css);
|
|
69
|
+
return runPurgecss(tree, strippedCss, purgecssOptions, purgecss, extractor).then((css)=>{
|
|
66
70
|
if (css.trim().length === 0) {
|
|
67
71
|
// @ts-expect-error -- explicitly remove the tag
|
|
68
72
|
styleNode.tag = false;
|
|
@@ -70,11 +74,11 @@ function processStyleNodePurgeCSS(tree, styleNode, purgecssOptions, purgecss) {
|
|
|
70
74
|
return;
|
|
71
75
|
}
|
|
72
76
|
styleNode.content = [
|
|
73
|
-
css
|
|
77
|
+
helpers_js.wrapCssCdata(css, isCdataWrapped)
|
|
74
78
|
];
|
|
75
79
|
});
|
|
76
80
|
}
|
|
77
|
-
function runPurgecss(tree, css, userOptions, purgecss) {
|
|
81
|
+
function runPurgecss(tree, css, userOptions, purgecss, extractor) {
|
|
78
82
|
if (typeof userOptions !== 'object') {
|
|
79
83
|
userOptions = {};
|
|
80
84
|
}
|
|
@@ -95,7 +99,7 @@ function runPurgecss(tree, css, userOptions, purgecss) {
|
|
|
95
99
|
],
|
|
96
100
|
extractors: [
|
|
97
101
|
{
|
|
98
|
-
extractor
|
|
102
|
+
extractor,
|
|
99
103
|
extensions: [
|
|
100
104
|
'html'
|
|
101
105
|
]
|
|
@@ -108,20 +112,26 @@ function runPurgecss(tree, css, userOptions, purgecss) {
|
|
|
108
112
|
}
|
|
109
113
|
/** Remove unused CSS */ const mod = {
|
|
110
114
|
async default (tree, options, userOptions) {
|
|
115
|
+
var _resolvedOptions_tool;
|
|
111
116
|
const promises = [];
|
|
112
117
|
let html;
|
|
118
|
+
let extractor;
|
|
113
119
|
const purgecss = await helpers_js.optionalImport('purgecss');
|
|
114
120
|
const uncss = await helpers_js.optionalImport('uncss');
|
|
121
|
+
const resolvedOptions = resolveUserOptions(userOptions);
|
|
122
|
+
const tool = (_resolvedOptions_tool = resolvedOptions.tool) != null ? _resolvedOptions_tool : 'purgeCSS';
|
|
123
|
+
const toolOptions = stripToolOption(resolvedOptions);
|
|
115
124
|
tree.walk((node)=>{
|
|
116
|
-
if (helpers_js.isStyleNode(node)) {
|
|
117
|
-
if (
|
|
125
|
+
if (helpers_js.isStyleNode(node) && helpers_js.isCssStyleType(node)) {
|
|
126
|
+
if (tool === 'purgeCSS') {
|
|
118
127
|
if (purgecss) {
|
|
119
|
-
|
|
128
|
+
extractor != null ? extractor : extractor = purgeFromHtml(tree);
|
|
129
|
+
promises.push(processStyleNodePurgeCSS(tree, node, toolOptions, purgecss, extractor));
|
|
120
130
|
}
|
|
121
|
-
} else {
|
|
131
|
+
} else if (tool === 'uncss') {
|
|
122
132
|
if (uncss) {
|
|
123
133
|
html != null ? html : html = tree.render(tree);
|
|
124
|
-
promises.push(processStyleNodeUnCSS(html, node,
|
|
134
|
+
promises.push(processStyleNodeUnCSS(html, node, toolOptions, uncss));
|
|
125
135
|
}
|
|
126
136
|
}
|
|
127
137
|
}
|
|
@@ -130,5 +140,21 @@ function runPurgecss(tree, css, userOptions, purgecss) {
|
|
|
130
140
|
return Promise.all(promises).then(()=>tree);
|
|
131
141
|
}
|
|
132
142
|
};
|
|
143
|
+
function getSelectorTokens(value) {
|
|
144
|
+
if (typeof value !== 'string') {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
return value.split(/\s+/).filter(Boolean);
|
|
148
|
+
}
|
|
149
|
+
function resolveUserOptions(userOptions) {
|
|
150
|
+
if (userOptions && typeof userOptions === 'object') {
|
|
151
|
+
return userOptions;
|
|
152
|
+
}
|
|
153
|
+
return {};
|
|
154
|
+
}
|
|
155
|
+
function stripToolOption(options) {
|
|
156
|
+
const { tool: _tool, ...rest } = options;
|
|
157
|
+
return rest;
|
|
158
|
+
}
|
|
133
159
|
|
|
134
160
|
exports.default = mod;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { optionalImport, isStyleNode, extractCssFromStyleNode } from '../helpers.mjs';
|
|
1
|
+
import { optionalImport, isStyleNode, isCssStyleType, extractCssFromStyleNode, stripCssCdata, wrapCssCdata } from '../helpers.mjs';
|
|
2
2
|
|
|
3
3
|
// These options must be set and shouldn't be overriden to ensure uncss doesn't look at linked stylesheets.
|
|
4
4
|
const uncssOptions = {
|
|
@@ -7,10 +7,12 @@ const uncssOptions = {
|
|
|
7
7
|
],
|
|
8
8
|
stylesheets: []
|
|
9
9
|
};
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- uncss has no types
|
|
10
11
|
function processStyleNodeUnCSS(html, styleNode, uncssOptions, uncss) {
|
|
11
12
|
const css = extractCssFromStyleNode(styleNode);
|
|
13
|
+
const { strippedCss, isCdataWrapped } = stripCssCdata(css);
|
|
12
14
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- uncss no types
|
|
13
|
-
return runUncss(html,
|
|
15
|
+
return runUncss(html, strippedCss, uncssOptions, uncss).then((css)=>{
|
|
14
16
|
// uncss may have left some style tags empty
|
|
15
17
|
if (css.trim().length === 0) {
|
|
16
18
|
// @ts-expect-error -- explicitly remove the tag
|
|
@@ -19,10 +21,11 @@ function processStyleNodeUnCSS(html, styleNode, uncssOptions, uncss) {
|
|
|
19
21
|
return;
|
|
20
22
|
}
|
|
21
23
|
styleNode.content = [
|
|
22
|
-
css
|
|
24
|
+
wrapCssCdata(css, isCdataWrapped)
|
|
23
25
|
];
|
|
24
26
|
});
|
|
25
27
|
}
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- uncss callback uses untyped args
|
|
26
29
|
function runUncss(html, css, userOptions, uncss) {
|
|
27
30
|
if (typeof userOptions !== 'object') {
|
|
28
31
|
userOptions = {};
|
|
@@ -48,8 +51,8 @@ const purgeFromHtml = function(tree) {
|
|
|
48
51
|
// making the process faster
|
|
49
52
|
const selectors = [];
|
|
50
53
|
tree.walk((node)=>{
|
|
51
|
-
const classes = node.attrs && node.attrs.class
|
|
52
|
-
const ids = node.attrs && node.attrs.id
|
|
54
|
+
const classes = getSelectorTokens(node.attrs && node.attrs.class);
|
|
55
|
+
const ids = getSelectorTokens(node.attrs && node.attrs.id);
|
|
53
56
|
selectors.push(...classes, ...ids);
|
|
54
57
|
if (node.tag) {
|
|
55
58
|
selectors.push(node.tag);
|
|
@@ -58,9 +61,10 @@ const purgeFromHtml = function(tree) {
|
|
|
58
61
|
});
|
|
59
62
|
return ()=>selectors;
|
|
60
63
|
};
|
|
61
|
-
function processStyleNodePurgeCSS(tree, styleNode, purgecssOptions, purgecss) {
|
|
64
|
+
function processStyleNodePurgeCSS(tree, styleNode, purgecssOptions, purgecss, extractor) {
|
|
62
65
|
const css = extractCssFromStyleNode(styleNode);
|
|
63
|
-
|
|
66
|
+
const { strippedCss, isCdataWrapped } = stripCssCdata(css);
|
|
67
|
+
return runPurgecss(tree, strippedCss, purgecssOptions, purgecss, extractor).then((css)=>{
|
|
64
68
|
if (css.trim().length === 0) {
|
|
65
69
|
// @ts-expect-error -- explicitly remove the tag
|
|
66
70
|
styleNode.tag = false;
|
|
@@ -68,11 +72,11 @@ function processStyleNodePurgeCSS(tree, styleNode, purgecssOptions, purgecss) {
|
|
|
68
72
|
return;
|
|
69
73
|
}
|
|
70
74
|
styleNode.content = [
|
|
71
|
-
css
|
|
75
|
+
wrapCssCdata(css, isCdataWrapped)
|
|
72
76
|
];
|
|
73
77
|
});
|
|
74
78
|
}
|
|
75
|
-
function runPurgecss(tree, css, userOptions, purgecss) {
|
|
79
|
+
function runPurgecss(tree, css, userOptions, purgecss, extractor) {
|
|
76
80
|
if (typeof userOptions !== 'object') {
|
|
77
81
|
userOptions = {};
|
|
78
82
|
}
|
|
@@ -93,7 +97,7 @@ function runPurgecss(tree, css, userOptions, purgecss) {
|
|
|
93
97
|
],
|
|
94
98
|
extractors: [
|
|
95
99
|
{
|
|
96
|
-
extractor
|
|
100
|
+
extractor,
|
|
97
101
|
extensions: [
|
|
98
102
|
'html'
|
|
99
103
|
]
|
|
@@ -106,20 +110,26 @@ function runPurgecss(tree, css, userOptions, purgecss) {
|
|
|
106
110
|
}
|
|
107
111
|
/** Remove unused CSS */ const mod = {
|
|
108
112
|
async default (tree, options, userOptions) {
|
|
113
|
+
var _resolvedOptions_tool;
|
|
109
114
|
const promises = [];
|
|
110
115
|
let html;
|
|
116
|
+
let extractor;
|
|
111
117
|
const purgecss = await optionalImport('purgecss');
|
|
112
118
|
const uncss = await optionalImport('uncss');
|
|
119
|
+
const resolvedOptions = resolveUserOptions(userOptions);
|
|
120
|
+
const tool = (_resolvedOptions_tool = resolvedOptions.tool) != null ? _resolvedOptions_tool : 'purgeCSS';
|
|
121
|
+
const toolOptions = stripToolOption(resolvedOptions);
|
|
113
122
|
tree.walk((node)=>{
|
|
114
|
-
if (isStyleNode(node)) {
|
|
115
|
-
if (
|
|
123
|
+
if (isStyleNode(node) && isCssStyleType(node)) {
|
|
124
|
+
if (tool === 'purgeCSS') {
|
|
116
125
|
if (purgecss) {
|
|
117
|
-
|
|
126
|
+
extractor != null ? extractor : extractor = purgeFromHtml(tree);
|
|
127
|
+
promises.push(processStyleNodePurgeCSS(tree, node, toolOptions, purgecss, extractor));
|
|
118
128
|
}
|
|
119
|
-
} else {
|
|
129
|
+
} else if (tool === 'uncss') {
|
|
120
130
|
if (uncss) {
|
|
121
131
|
html != null ? html : html = tree.render(tree);
|
|
122
|
-
promises.push(processStyleNodeUnCSS(html, node,
|
|
132
|
+
promises.push(processStyleNodeUnCSS(html, node, toolOptions, uncss));
|
|
123
133
|
}
|
|
124
134
|
}
|
|
125
135
|
}
|
|
@@ -128,5 +138,21 @@ function runPurgecss(tree, css, userOptions, purgecss) {
|
|
|
128
138
|
return Promise.all(promises).then(()=>tree);
|
|
129
139
|
}
|
|
130
140
|
};
|
|
141
|
+
function getSelectorTokens(value) {
|
|
142
|
+
if (typeof value !== 'string') {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
return value.split(/\s+/).filter(Boolean);
|
|
146
|
+
}
|
|
147
|
+
function resolveUserOptions(userOptions) {
|
|
148
|
+
if (userOptions && typeof userOptions === 'object') {
|
|
149
|
+
return userOptions;
|
|
150
|
+
}
|
|
151
|
+
return {};
|
|
152
|
+
}
|
|
153
|
+
function stripToolOption(options) {
|
|
154
|
+
const { tool: _tool, ...rest } = options;
|
|
155
|
+
return rest;
|
|
156
|
+
}
|
|
131
157
|
|
|
132
158
|
export { mod as default };
|