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
|
@@ -6,6 +6,8 @@ var helpers_js = require('../helpers.js');
|
|
|
6
6
|
async default (tree, options, svgoOptions) {
|
|
7
7
|
const svgo = await helpers_js.optionalImport('svgo');
|
|
8
8
|
if (!svgo) return tree;
|
|
9
|
+
const resolvedSvgoOptions = resolveSvgoOptions(svgoOptions);
|
|
10
|
+
const svgoOptionsWithDefaults = applySvgoDefaults(resolvedSvgoOptions);
|
|
9
11
|
tree.match({
|
|
10
12
|
tag: 'svg'
|
|
11
13
|
}, (node)=>{
|
|
@@ -14,7 +16,7 @@ var helpers_js = require('../helpers.js');
|
|
|
14
16
|
quoteAllAttributes: true
|
|
15
17
|
});
|
|
16
18
|
try {
|
|
17
|
-
const result = svgo.optimize(svgStr,
|
|
19
|
+
const result = svgo.optimize(svgStr, svgoOptionsWithDefaults);
|
|
18
20
|
// @ts-expect-error -- remove this node
|
|
19
21
|
node.tag = false;
|
|
20
22
|
node.attrs = {};
|
|
@@ -25,9 +27,25 @@ var helpers_js = require('../helpers.js');
|
|
|
25
27
|
return node;
|
|
26
28
|
} catch (error) {
|
|
27
29
|
const isSvgoParserError = Boolean(error && typeof error === 'object' && 'name' in error && error.name === 'SvgoParserError');
|
|
28
|
-
if (!
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
if (!isSvgoParserError) {
|
|
31
|
+
if (!options.skipInternalWarnings) {
|
|
32
|
+
console.error('htmlnano fails to minify the svg:');
|
|
33
|
+
console.error(error);
|
|
34
|
+
}
|
|
35
|
+
let fallbackSvgStr = svgStr;
|
|
36
|
+
try {
|
|
37
|
+
fallbackSvgStr = svgo.optimize(svgStr, {
|
|
38
|
+
plugins: []
|
|
39
|
+
}).data;
|
|
40
|
+
} catch (unused) {
|
|
41
|
+
fallbackSvgStr = svgStr;
|
|
42
|
+
}
|
|
43
|
+
// @ts-expect-error -- remove this node
|
|
44
|
+
node.tag = false;
|
|
45
|
+
node.attrs = {};
|
|
46
|
+
node.content = [
|
|
47
|
+
fallbackSvgStr
|
|
48
|
+
];
|
|
31
49
|
}
|
|
32
50
|
// We return the node as-is
|
|
33
51
|
return node;
|
|
@@ -36,5 +54,18 @@ var helpers_js = require('../helpers.js');
|
|
|
36
54
|
return tree;
|
|
37
55
|
}
|
|
38
56
|
};
|
|
57
|
+
function resolveSvgoOptions(svgoOptions) {
|
|
58
|
+
if (!svgoOptions || svgoOptions === true) {
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
return svgoOptions;
|
|
62
|
+
}
|
|
63
|
+
function applySvgoDefaults(svgoOptions) {
|
|
64
|
+
var _svgoOptions_multipass;
|
|
65
|
+
return {
|
|
66
|
+
...svgoOptions,
|
|
67
|
+
multipass: (_svgoOptions_multipass = svgoOptions.multipass) != null ? _svgoOptions_multipass : true
|
|
68
|
+
};
|
|
69
|
+
}
|
|
39
70
|
|
|
40
71
|
exports.default = mod;
|
|
@@ -4,6 +4,8 @@ import { optionalImport } from '../helpers.mjs';
|
|
|
4
4
|
async default (tree, options, svgoOptions) {
|
|
5
5
|
const svgo = await optionalImport('svgo');
|
|
6
6
|
if (!svgo) return tree;
|
|
7
|
+
const resolvedSvgoOptions = resolveSvgoOptions(svgoOptions);
|
|
8
|
+
const svgoOptionsWithDefaults = applySvgoDefaults(resolvedSvgoOptions);
|
|
7
9
|
tree.match({
|
|
8
10
|
tag: 'svg'
|
|
9
11
|
}, (node)=>{
|
|
@@ -12,7 +14,7 @@ import { optionalImport } from '../helpers.mjs';
|
|
|
12
14
|
quoteAllAttributes: true
|
|
13
15
|
});
|
|
14
16
|
try {
|
|
15
|
-
const result = svgo.optimize(svgStr,
|
|
17
|
+
const result = svgo.optimize(svgStr, svgoOptionsWithDefaults);
|
|
16
18
|
// @ts-expect-error -- remove this node
|
|
17
19
|
node.tag = false;
|
|
18
20
|
node.attrs = {};
|
|
@@ -23,9 +25,25 @@ import { optionalImport } from '../helpers.mjs';
|
|
|
23
25
|
return node;
|
|
24
26
|
} catch (error) {
|
|
25
27
|
const isSvgoParserError = Boolean(error && typeof error === 'object' && 'name' in error && error.name === 'SvgoParserError');
|
|
26
|
-
if (!
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
if (!isSvgoParserError) {
|
|
29
|
+
if (!options.skipInternalWarnings) {
|
|
30
|
+
console.error('htmlnano fails to minify the svg:');
|
|
31
|
+
console.error(error);
|
|
32
|
+
}
|
|
33
|
+
let fallbackSvgStr = svgStr;
|
|
34
|
+
try {
|
|
35
|
+
fallbackSvgStr = svgo.optimize(svgStr, {
|
|
36
|
+
plugins: []
|
|
37
|
+
}).data;
|
|
38
|
+
} catch (unused) {
|
|
39
|
+
fallbackSvgStr = svgStr;
|
|
40
|
+
}
|
|
41
|
+
// @ts-expect-error -- remove this node
|
|
42
|
+
node.tag = false;
|
|
43
|
+
node.attrs = {};
|
|
44
|
+
node.content = [
|
|
45
|
+
fallbackSvgStr
|
|
46
|
+
];
|
|
29
47
|
}
|
|
30
48
|
// We return the node as-is
|
|
31
49
|
return node;
|
|
@@ -34,5 +52,18 @@ import { optionalImport } from '../helpers.mjs';
|
|
|
34
52
|
return tree;
|
|
35
53
|
}
|
|
36
54
|
};
|
|
55
|
+
function resolveSvgoOptions(svgoOptions) {
|
|
56
|
+
if (!svgoOptions || svgoOptions === true) {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
return svgoOptions;
|
|
60
|
+
}
|
|
61
|
+
function applySvgoDefaults(svgoOptions) {
|
|
62
|
+
var _svgoOptions_multipass;
|
|
63
|
+
return {
|
|
64
|
+
...svgoOptions,
|
|
65
|
+
multipass: (_svgoOptions_multipass = svgoOptions.multipass) != null ? _svgoOptions_multipass : true
|
|
66
|
+
};
|
|
67
|
+
}
|
|
37
68
|
|
|
38
69
|
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,6 @@ type HtmlnanoModule<Options = any> = {
|
|
|
52
85
|
default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
|
|
53
86
|
};
|
|
54
87
|
|
|
55
|
-
declare const mod: HtmlnanoModule
|
|
88
|
+
declare const mod: HtmlnanoModule<HtmlnanoOptions['minifyUrls']>;
|
|
56
89
|
|
|
57
90
|
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,6 @@ type HtmlnanoModule<Options = any> = {
|
|
|
52
85
|
default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
|
|
53
86
|
};
|
|
54
87
|
|
|
55
|
-
declare const mod: HtmlnanoModule
|
|
88
|
+
declare const mod: HtmlnanoModule<HtmlnanoOptions['minifyUrls']>;
|
|
56
89
|
|
|
57
90
|
export { mod as default };
|
|
@@ -118,21 +118,19 @@ let STORED_URL_BASE;
|
|
|
118
118
|
for (const [attrName, attrValue] of Object.entries(node.attrs)){
|
|
119
119
|
const attrNameLower = attrName.toLowerCase();
|
|
120
120
|
if (isUriTypeAttribute(node.tag, attrNameLower)) {
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
node.attrs[attrName] = relateUrlInstance.relate(attrValue);
|
|
130
|
-
}
|
|
121
|
+
if (typeof attrValue !== 'string') continue;
|
|
122
|
+
const javascriptMatch = getJavaScriptUrlMatch(attrValue);
|
|
123
|
+
if (javascriptMatch) {
|
|
124
|
+
promises.push(minifyJavaScriptUrl(node, attrName, javascriptMatch, terser));
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (relateUrlInstance) {
|
|
128
|
+
node.attrs[attrName] = relateUrlValue(relateUrlInstance, attrValue);
|
|
131
129
|
}
|
|
132
130
|
continue;
|
|
133
131
|
}
|
|
134
132
|
if (isSrcsetAttribute(node.tag, attrNameLower)) {
|
|
135
|
-
if (srcset && attrValue) {
|
|
133
|
+
if (srcset && typeof attrValue === 'string') {
|
|
136
134
|
try {
|
|
137
135
|
const parsedSrcset = srcset.parseSrcset(attrValue, {
|
|
138
136
|
strict: true
|
|
@@ -140,7 +138,7 @@ let STORED_URL_BASE;
|
|
|
140
138
|
node.attrs[attrName] = srcset.stringifySrcset(parsedSrcset.map((item)=>{
|
|
141
139
|
if (relateUrlInstance) {
|
|
142
140
|
// @ts-expect-error -- not actually readonly
|
|
143
|
-
item.url = relateUrlInstance
|
|
141
|
+
item.url = relateUrlValue(relateUrlInstance, item.url);
|
|
144
142
|
}
|
|
145
143
|
return item;
|
|
146
144
|
}));
|
|
@@ -157,24 +155,51 @@ let STORED_URL_BASE;
|
|
|
157
155
|
return Promise.resolve(tree);
|
|
158
156
|
}
|
|
159
157
|
};
|
|
160
|
-
function isJavaScriptUrl(url) {
|
|
161
|
-
return typeof url === 'string' && url.toLowerCase().startsWith(JAVASCRIPT_URL_PROTOCOL);
|
|
162
|
-
}
|
|
163
158
|
const jsWrapperStart = 'function a(){';
|
|
164
159
|
const jsWrapperEnd = '}a();';
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
160
|
+
const javascriptUrlRegex = /^(\s*)(javascript:)([\s\S]*)$/i;
|
|
161
|
+
function getJavaScriptUrlMatch(url) {
|
|
162
|
+
const match = javascriptUrlRegex.exec(url);
|
|
163
|
+
if (!match) return null;
|
|
164
|
+
return {
|
|
165
|
+
leadingWhitespace: match[1],
|
|
166
|
+
code: match[3]
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function getUrlScheme(value) {
|
|
170
|
+
const match = /^[a-z][a-z0-9+.-]*:/i.exec(value);
|
|
171
|
+
if (!match) return null;
|
|
172
|
+
return match[0].slice(0, -1).toLowerCase();
|
|
173
|
+
}
|
|
174
|
+
function shouldRelateUrlValue(value) {
|
|
175
|
+
const trimmed = value.trim();
|
|
176
|
+
if (!trimmed) return false;
|
|
177
|
+
if (trimmed.startsWith('#') || trimmed.startsWith('?')) return false;
|
|
178
|
+
const scheme = getUrlScheme(trimmed);
|
|
179
|
+
if (scheme) return scheme === 'http' || scheme === 'https';
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
function relateUrlValue(relateUrl, value) {
|
|
183
|
+
if (!shouldRelateUrlValue(value)) return value;
|
|
184
|
+
// FIXME!
|
|
185
|
+
// relateurl@1.0.0-alpha only supports URL while stable version (0.2.7) only supports string
|
|
186
|
+
// the WHATWG URL API is very strict while attrValue might not be a valid URL
|
|
187
|
+
// new URL should be used, and relateUrl#relate should be wrapped in try...catch after relateurl@1 is stable
|
|
188
|
+
try {
|
|
189
|
+
return relateUrl.relate(value);
|
|
190
|
+
} catch (unused) {
|
|
191
|
+
return value;
|
|
176
192
|
}
|
|
177
|
-
|
|
193
|
+
}
|
|
194
|
+
function minifyJavaScriptUrl(node, attrName, match, terser) {
|
|
195
|
+
if (!terser) return Promise.resolve();
|
|
196
|
+
const result = jsWrapperStart + match.code + jsWrapperEnd;
|
|
197
|
+
return terser.minify(result, {}) // Default Option is good enough
|
|
198
|
+
.then(({ code })=>{
|
|
199
|
+
if (!code) return;
|
|
200
|
+
const minifiedJs = code.substring(jsWrapperStart.length, code.length - jsWrapperEnd.length);
|
|
201
|
+
node.attrs[attrName] = match.leadingWhitespace + JAVASCRIPT_URL_PROTOCOL + minifiedJs;
|
|
202
|
+
}).catch(()=>undefined);
|
|
178
203
|
}
|
|
179
204
|
|
|
180
205
|
exports.default = mod;
|
|
@@ -116,21 +116,19 @@ let STORED_URL_BASE;
|
|
|
116
116
|
for (const [attrName, attrValue] of Object.entries(node.attrs)){
|
|
117
117
|
const attrNameLower = attrName.toLowerCase();
|
|
118
118
|
if (isUriTypeAttribute(node.tag, attrNameLower)) {
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
node.attrs[attrName] = relateUrlInstance.relate(attrValue);
|
|
128
|
-
}
|
|
119
|
+
if (typeof attrValue !== 'string') continue;
|
|
120
|
+
const javascriptMatch = getJavaScriptUrlMatch(attrValue);
|
|
121
|
+
if (javascriptMatch) {
|
|
122
|
+
promises.push(minifyJavaScriptUrl(node, attrName, javascriptMatch, terser));
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (relateUrlInstance) {
|
|
126
|
+
node.attrs[attrName] = relateUrlValue(relateUrlInstance, attrValue);
|
|
129
127
|
}
|
|
130
128
|
continue;
|
|
131
129
|
}
|
|
132
130
|
if (isSrcsetAttribute(node.tag, attrNameLower)) {
|
|
133
|
-
if (srcset && attrValue) {
|
|
131
|
+
if (srcset && typeof attrValue === 'string') {
|
|
134
132
|
try {
|
|
135
133
|
const parsedSrcset = srcset.parseSrcset(attrValue, {
|
|
136
134
|
strict: true
|
|
@@ -138,7 +136,7 @@ let STORED_URL_BASE;
|
|
|
138
136
|
node.attrs[attrName] = srcset.stringifySrcset(parsedSrcset.map((item)=>{
|
|
139
137
|
if (relateUrlInstance) {
|
|
140
138
|
// @ts-expect-error -- not actually readonly
|
|
141
|
-
item.url = relateUrlInstance
|
|
139
|
+
item.url = relateUrlValue(relateUrlInstance, item.url);
|
|
142
140
|
}
|
|
143
141
|
return item;
|
|
144
142
|
}));
|
|
@@ -155,24 +153,51 @@ let STORED_URL_BASE;
|
|
|
155
153
|
return Promise.resolve(tree);
|
|
156
154
|
}
|
|
157
155
|
};
|
|
158
|
-
function isJavaScriptUrl(url) {
|
|
159
|
-
return typeof url === 'string' && url.toLowerCase().startsWith(JAVASCRIPT_URL_PROTOCOL);
|
|
160
|
-
}
|
|
161
156
|
const jsWrapperStart = 'function a(){';
|
|
162
157
|
const jsWrapperEnd = '}a();';
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
158
|
+
const javascriptUrlRegex = /^(\s*)(javascript:)([\s\S]*)$/i;
|
|
159
|
+
function getJavaScriptUrlMatch(url) {
|
|
160
|
+
const match = javascriptUrlRegex.exec(url);
|
|
161
|
+
if (!match) return null;
|
|
162
|
+
return {
|
|
163
|
+
leadingWhitespace: match[1],
|
|
164
|
+
code: match[3]
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function getUrlScheme(value) {
|
|
168
|
+
const match = /^[a-z][a-z0-9+.-]*:/i.exec(value);
|
|
169
|
+
if (!match) return null;
|
|
170
|
+
return match[0].slice(0, -1).toLowerCase();
|
|
171
|
+
}
|
|
172
|
+
function shouldRelateUrlValue(value) {
|
|
173
|
+
const trimmed = value.trim();
|
|
174
|
+
if (!trimmed) return false;
|
|
175
|
+
if (trimmed.startsWith('#') || trimmed.startsWith('?')) return false;
|
|
176
|
+
const scheme = getUrlScheme(trimmed);
|
|
177
|
+
if (scheme) return scheme === 'http' || scheme === 'https';
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
function relateUrlValue(relateUrl, value) {
|
|
181
|
+
if (!shouldRelateUrlValue(value)) return value;
|
|
182
|
+
// FIXME!
|
|
183
|
+
// relateurl@1.0.0-alpha only supports URL while stable version (0.2.7) only supports string
|
|
184
|
+
// the WHATWG URL API is very strict while attrValue might not be a valid URL
|
|
185
|
+
// new URL should be used, and relateUrl#relate should be wrapped in try...catch after relateurl@1 is stable
|
|
186
|
+
try {
|
|
187
|
+
return relateUrl.relate(value);
|
|
188
|
+
} catch (unused) {
|
|
189
|
+
return value;
|
|
174
190
|
}
|
|
175
|
-
|
|
191
|
+
}
|
|
192
|
+
function minifyJavaScriptUrl(node, attrName, match, terser) {
|
|
193
|
+
if (!terser) return Promise.resolve();
|
|
194
|
+
const result = jsWrapperStart + match.code + jsWrapperEnd;
|
|
195
|
+
return terser.minify(result, {}) // Default Option is good enough
|
|
196
|
+
.then(({ code })=>{
|
|
197
|
+
if (!code) return;
|
|
198
|
+
const minifiedJs = code.substring(jsWrapperStart.length, code.length - jsWrapperEnd.length);
|
|
199
|
+
node.attrs[attrName] = match.leadingWhitespace + JAVASCRIPT_URL_PROTOCOL + minifiedJs;
|
|
200
|
+
}).catch(()=>undefined);
|
|
176
201
|
}
|
|
177
202
|
|
|
178
203
|
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
|
}
|
|
@@ -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
|
}
|