htmlnano 3.2.0 → 3.2.1

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.
Files changed (79) hide show
  1. package/dist/_modules/collapseAttributeWhitespace.d.mts +58 -14
  2. package/dist/_modules/collapseAttributeWhitespace.d.ts +58 -14
  3. package/dist/_modules/collapseBooleanAttributes.d.mts +58 -14
  4. package/dist/_modules/collapseBooleanAttributes.d.ts +58 -14
  5. package/dist/_modules/collapseWhitespace.d.mts +58 -14
  6. package/dist/_modules/collapseWhitespace.d.ts +58 -14
  7. package/dist/_modules/custom.d.mts +58 -14
  8. package/dist/_modules/custom.d.ts +58 -14
  9. package/dist/_modules/deduplicateAttributeValues.d.mts +58 -14
  10. package/dist/_modules/deduplicateAttributeValues.d.ts +58 -14
  11. package/dist/_modules/example.d.mts +58 -14
  12. package/dist/_modules/example.d.ts +58 -14
  13. package/dist/_modules/mergeScripts.d.mts +58 -14
  14. package/dist/_modules/mergeScripts.d.ts +58 -14
  15. package/dist/_modules/mergeScripts.js +1 -6
  16. package/dist/_modules/mergeScripts.mjs +1 -6
  17. package/dist/_modules/mergeStyles.d.mts +58 -14
  18. package/dist/_modules/mergeStyles.d.ts +58 -14
  19. package/dist/_modules/mergeStyles.js +1 -6
  20. package/dist/_modules/mergeStyles.mjs +1 -6
  21. package/dist/_modules/minifyAttributes.d.mts +58 -14
  22. package/dist/_modules/minifyAttributes.d.ts +58 -14
  23. package/dist/_modules/minifyAttributes.js +2 -2
  24. package/dist/_modules/minifyAttributes.mjs +2 -2
  25. package/dist/_modules/minifyConditionalComments.d.mts +58 -14
  26. package/dist/_modules/minifyConditionalComments.d.ts +58 -14
  27. package/dist/_modules/minifyCss.d.mts +61 -14
  28. package/dist/_modules/minifyCss.d.ts +61 -14
  29. package/dist/_modules/minifyCss.js +79 -12
  30. package/dist/_modules/minifyCss.mjs +79 -13
  31. package/dist/_modules/minifyHtmlTemplate.d.mts +58 -14
  32. package/dist/_modules/minifyHtmlTemplate.d.ts +58 -14
  33. package/dist/_modules/minifyJs.d.mts +58 -13
  34. package/dist/_modules/minifyJs.d.ts +58 -13
  35. package/dist/_modules/minifyJson.d.mts +58 -14
  36. package/dist/_modules/minifyJson.d.ts +58 -14
  37. package/dist/_modules/minifyJson.js +1 -1
  38. package/dist/_modules/minifyJson.mjs +1 -1
  39. package/dist/_modules/minifySvg.d.mts +58 -13
  40. package/dist/_modules/minifySvg.d.ts +58 -13
  41. package/dist/_modules/minifySvg.js +2 -2
  42. package/dist/_modules/minifySvg.mjs +2 -2
  43. package/dist/_modules/minifyUrls.d.mts +58 -14
  44. package/dist/_modules/minifyUrls.d.ts +58 -14
  45. package/dist/_modules/minifyUrls.js +2 -2
  46. package/dist/_modules/minifyUrls.mjs +2 -2
  47. package/dist/_modules/normalizeAttributeValues.d.mts +58 -14
  48. package/dist/_modules/normalizeAttributeValues.d.ts +58 -14
  49. package/dist/_modules/removeAttributeQuotes.d.mts +58 -14
  50. package/dist/_modules/removeAttributeQuotes.d.ts +58 -14
  51. package/dist/_modules/removeComments.d.mts +58 -14
  52. package/dist/_modules/removeComments.d.ts +58 -14
  53. package/dist/_modules/removeComments.js +1 -1
  54. package/dist/_modules/removeComments.mjs +1 -1
  55. package/dist/_modules/removeEmptyAttributes.d.mts +58 -14
  56. package/dist/_modules/removeEmptyAttributes.d.ts +58 -14
  57. package/dist/_modules/removeEmptyElements.d.mts +58 -14
  58. package/dist/_modules/removeEmptyElements.d.ts +58 -14
  59. package/dist/_modules/removeOptionalTags.d.mts +58 -14
  60. package/dist/_modules/removeOptionalTags.d.ts +58 -14
  61. package/dist/_modules/removeRedundantAttributes.d.mts +58 -14
  62. package/dist/_modules/removeRedundantAttributes.d.ts +58 -14
  63. package/dist/_modules/removeUnusedCss.d.mts +58 -14
  64. package/dist/_modules/removeUnusedCss.d.ts +58 -14
  65. package/dist/_modules/sortAttributes.d.mts +58 -14
  66. package/dist/_modules/sortAttributes.d.ts +58 -14
  67. package/dist/_modules/sortAttributes.js +1 -6
  68. package/dist/_modules/sortAttributes.mjs +1 -6
  69. package/dist/_modules/sortAttributesWithLists.d.mts +58 -14
  70. package/dist/_modules/sortAttributesWithLists.d.ts +58 -14
  71. package/dist/index.d.ts +59 -15
  72. package/dist/index.js +7 -6
  73. package/dist/index.mjs +7 -6
  74. package/dist/presets/ampSafe.d.ts +57 -12
  75. package/dist/presets/max.d.ts +57 -12
  76. package/dist/presets/safe.d.ts +57 -12
  77. package/dist/presets/safe.js +1 -1
  78. package/dist/presets/safe.mjs +1 -1
  79. package/package.json +14 -12
@@ -1,25 +1,71 @@
1
1
  import PostHTML from 'posthtml';
2
- import { MinifyOptions } from 'terser';
3
2
  import { Options } from 'cssnano';
4
- import { Config } from 'svgo';
5
- import { UserDefinedOptions } from 'purgecss';
6
3
 
7
- type PostHTMLTreeLike = [PostHTML.Node] & PostHTML.NodeAPI & {
4
+ type PostHTMLNodeLike = PostHTML.Node | string;
5
+ type PostHTMLTreeLike = [PostHTMLNodeLike] & PostHTML.NodeAPI & {
8
6
  options?: {
9
7
  quoteAllAttributes?: boolean | undefined;
10
8
  quoteStyle?: 0 | 1 | 2 | undefined;
11
9
  replaceQuote?: boolean | undefined;
12
10
  } | undefined;
13
11
  render(): string;
14
- render(node: PostHTML.Node | PostHTMLTreeLike, renderOptions?: any): string;
12
+ render(node: PostHTMLNodeLike | PostHTMLTreeLike, renderOptions?: any): string;
15
13
  };
16
14
  type MaybeArray<T> = T | Array<T>;
17
- type PostHTMLNodeLike = PostHTML.Node | string;
18
15
  type HtmlnanoTemplateRule = {
19
16
  tag: string;
20
17
  attrs?: Record<string, string | boolean | void>;
21
18
  };
22
19
  type MinifyHtmlTemplateOptions = boolean | HtmlnanoTemplateRule[];
20
+ type HtmlnanoMinifyCssOptions = object;
21
+ type HtmlnanoMinifyJsOptions = object;
22
+ type HtmlnanoMinifySvgOptions = object;
23
+ type HtmlnanoPurgeCssPattern = string | RegExp;
24
+ type HtmlnanoPurgeCssExtractorResultDetailed = {
25
+ attributes: {
26
+ names: string[];
27
+ values: string[];
28
+ };
29
+ classes: string[];
30
+ ids: string[];
31
+ tags: string[];
32
+ undetermined: string[];
33
+ };
34
+ type HtmlnanoPurgeCssExtractorResult = HtmlnanoPurgeCssExtractorResultDetailed | string[];
35
+ type HtmlnanoPurgeCssDefaultExtractor = (content: string) => HtmlnanoPurgeCssExtractorResult;
36
+ type HtmlnanoPurgeCssSourceMapOptions = {
37
+ absolute?: boolean;
38
+ annotation?: boolean | string;
39
+ from?: string;
40
+ inline?: boolean;
41
+ prev?: boolean | object | string;
42
+ sourcesContent?: boolean;
43
+ to?: string;
44
+ };
45
+ type HtmlnanoPurgeCssSafelist = HtmlnanoPurgeCssPattern[] | {
46
+ standard?: HtmlnanoPurgeCssPattern[];
47
+ deep?: RegExp[];
48
+ greedy?: RegExp[];
49
+ variables?: HtmlnanoPurgeCssPattern[];
50
+ keyframes?: HtmlnanoPurgeCssPattern[];
51
+ };
52
+ interface HtmlnanoPurgeCssOptions {
53
+ tool: 'purgeCSS';
54
+ defaultExtractor?: HtmlnanoPurgeCssDefaultExtractor;
55
+ fontFace?: boolean;
56
+ keyframes?: boolean;
57
+ output?: string;
58
+ rejected?: boolean;
59
+ rejectedCss?: boolean;
60
+ sourceMap?: boolean | HtmlnanoPurgeCssSourceMapOptions;
61
+ stdin?: boolean;
62
+ stdout?: boolean;
63
+ variables?: boolean;
64
+ safelist?: HtmlnanoPurgeCssSafelist;
65
+ blocklist?: HtmlnanoPurgeCssPattern[];
66
+ skippedContentGlobs?: string[];
67
+ dynamicAttributes?: string[];
68
+ }
23
69
  interface HtmlnanoOptions {
24
70
  skipConfigLoading?: boolean;
25
71
  configPath?: string;
@@ -34,16 +80,16 @@ interface HtmlnanoOptions {
34
80
  minifyUrls?: URL | string | false;
35
81
  mergeStyles?: boolean;
36
82
  mergeScripts?: boolean;
37
- minifyCss?: Options | boolean;
83
+ minifyCss?: HtmlnanoMinifyCssOptions | boolean;
38
84
  minifyHtmlTemplate?: MinifyHtmlTemplateOptions;
39
85
  minifyConditionalComments?: boolean;
40
- minifyJs?: MinifyOptions | boolean;
86
+ minifyJs?: HtmlnanoMinifyJsOptions | boolean;
41
87
  minifyJson?: boolean;
42
88
  minifyAttributes?: boolean | {
43
89
  metaContent?: boolean;
44
90
  redundantWhitespaces?: 'safe' | 'agressive' | false;
45
91
  };
46
- minifySvg?: Config | boolean;
92
+ minifySvg?: HtmlnanoMinifySvgOptions | boolean;
47
93
  normalizeAttributeValues?: boolean;
48
94
  removeAttributeQuotes?: boolean | {
49
95
  force?: boolean;
@@ -55,9 +101,8 @@ interface HtmlnanoOptions {
55
101
  };
56
102
  removeRedundantAttributes?: boolean;
57
103
  removeOptionalTags?: boolean;
58
- removeUnusedCss?: boolean | ({
59
- tool: 'purgeCSS';
60
- } & Omit<UserDefinedOptions, 'content' | 'css' | 'extractors'>) | {
104
+ removeUnusedCss?: boolean | HtmlnanoPurgeCssOptions | {
105
+ tool?: 'uncss';
61
106
  banner?: boolean;
62
107
  csspath?: string;
63
108
  htmlroot?: string;
@@ -77,7 +122,7 @@ interface HtmlnanoOptions {
77
122
  type HtmlnanoModuleAttrsHandler = (attrs: Record<string, string | boolean | void>, node: PostHTML.Node) => Record<string, string | boolean | void>;
78
123
  type HtmlnanoModuleContentHandler = (content: Array<PostHTMLNodeLike>, node: PostHTML.Node) => MaybeArray<PostHTMLNodeLike>;
79
124
  type HtmlnanoModuleNodeHandler = (node: PostHTMLNodeLike) => PostHTML.Node | string;
80
- type OptionalOptions<T> = T extends boolean | string | Function | number | null | undefined ? T : T extends object ? Partial<T> : T;
125
+ type OptionalOptions<T> = T extends boolean | string | Function | number | null | undefined ? T : T extends Array<infer Item> ? Array<Item> : T extends object ? Partial<T> : T;
81
126
  type HtmlnanoModule<Options = any> = {
82
127
  onAttrs?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleAttrsHandler;
83
128
  onContent?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleContentHandler;
@@ -87,4 +132,6 @@ type HtmlnanoModule<Options = any> = {
87
132
 
88
133
  declare const mod: HtmlnanoModule<Options>;
89
134
 
90
- export { mod as default };
135
+ declare function getInlineCssnanoOptions(cssnanoOptions: Options | undefined): Options | undefined;
136
+
137
+ export { mod as default, getInlineCssnanoOptions };
@@ -1,25 +1,71 @@
1
1
  import PostHTML from 'posthtml';
2
- import { MinifyOptions } from 'terser';
3
2
  import { Options } from 'cssnano';
4
- import { Config } from 'svgo';
5
- import { UserDefinedOptions } from 'purgecss';
6
3
 
7
- type PostHTMLTreeLike = [PostHTML.Node] & PostHTML.NodeAPI & {
4
+ type PostHTMLNodeLike = PostHTML.Node | string;
5
+ type PostHTMLTreeLike = [PostHTMLNodeLike] & PostHTML.NodeAPI & {
8
6
  options?: {
9
7
  quoteAllAttributes?: boolean | undefined;
10
8
  quoteStyle?: 0 | 1 | 2 | undefined;
11
9
  replaceQuote?: boolean | undefined;
12
10
  } | undefined;
13
11
  render(): string;
14
- render(node: PostHTML.Node | PostHTMLTreeLike, renderOptions?: any): string;
12
+ render(node: PostHTMLNodeLike | PostHTMLTreeLike, renderOptions?: any): string;
15
13
  };
16
14
  type MaybeArray<T> = T | Array<T>;
17
- type PostHTMLNodeLike = PostHTML.Node | string;
18
15
  type HtmlnanoTemplateRule = {
19
16
  tag: string;
20
17
  attrs?: Record<string, string | boolean | void>;
21
18
  };
22
19
  type MinifyHtmlTemplateOptions = boolean | HtmlnanoTemplateRule[];
20
+ type HtmlnanoMinifyCssOptions = object;
21
+ type HtmlnanoMinifyJsOptions = object;
22
+ type HtmlnanoMinifySvgOptions = object;
23
+ type HtmlnanoPurgeCssPattern = string | RegExp;
24
+ type HtmlnanoPurgeCssExtractorResultDetailed = {
25
+ attributes: {
26
+ names: string[];
27
+ values: string[];
28
+ };
29
+ classes: string[];
30
+ ids: string[];
31
+ tags: string[];
32
+ undetermined: string[];
33
+ };
34
+ type HtmlnanoPurgeCssExtractorResult = HtmlnanoPurgeCssExtractorResultDetailed | string[];
35
+ type HtmlnanoPurgeCssDefaultExtractor = (content: string) => HtmlnanoPurgeCssExtractorResult;
36
+ type HtmlnanoPurgeCssSourceMapOptions = {
37
+ absolute?: boolean;
38
+ annotation?: boolean | string;
39
+ from?: string;
40
+ inline?: boolean;
41
+ prev?: boolean | object | string;
42
+ sourcesContent?: boolean;
43
+ to?: string;
44
+ };
45
+ type HtmlnanoPurgeCssSafelist = HtmlnanoPurgeCssPattern[] | {
46
+ standard?: HtmlnanoPurgeCssPattern[];
47
+ deep?: RegExp[];
48
+ greedy?: RegExp[];
49
+ variables?: HtmlnanoPurgeCssPattern[];
50
+ keyframes?: HtmlnanoPurgeCssPattern[];
51
+ };
52
+ interface HtmlnanoPurgeCssOptions {
53
+ tool: 'purgeCSS';
54
+ defaultExtractor?: HtmlnanoPurgeCssDefaultExtractor;
55
+ fontFace?: boolean;
56
+ keyframes?: boolean;
57
+ output?: string;
58
+ rejected?: boolean;
59
+ rejectedCss?: boolean;
60
+ sourceMap?: boolean | HtmlnanoPurgeCssSourceMapOptions;
61
+ stdin?: boolean;
62
+ stdout?: boolean;
63
+ variables?: boolean;
64
+ safelist?: HtmlnanoPurgeCssSafelist;
65
+ blocklist?: HtmlnanoPurgeCssPattern[];
66
+ skippedContentGlobs?: string[];
67
+ dynamicAttributes?: string[];
68
+ }
23
69
  interface HtmlnanoOptions {
24
70
  skipConfigLoading?: boolean;
25
71
  configPath?: string;
@@ -34,16 +80,16 @@ interface HtmlnanoOptions {
34
80
  minifyUrls?: URL | string | false;
35
81
  mergeStyles?: boolean;
36
82
  mergeScripts?: boolean;
37
- minifyCss?: Options | boolean;
83
+ minifyCss?: HtmlnanoMinifyCssOptions | boolean;
38
84
  minifyHtmlTemplate?: MinifyHtmlTemplateOptions;
39
85
  minifyConditionalComments?: boolean;
40
- minifyJs?: MinifyOptions | boolean;
86
+ minifyJs?: HtmlnanoMinifyJsOptions | boolean;
41
87
  minifyJson?: boolean;
42
88
  minifyAttributes?: boolean | {
43
89
  metaContent?: boolean;
44
90
  redundantWhitespaces?: 'safe' | 'agressive' | false;
45
91
  };
46
- minifySvg?: Config | boolean;
92
+ minifySvg?: HtmlnanoMinifySvgOptions | boolean;
47
93
  normalizeAttributeValues?: boolean;
48
94
  removeAttributeQuotes?: boolean | {
49
95
  force?: boolean;
@@ -55,9 +101,8 @@ interface HtmlnanoOptions {
55
101
  };
56
102
  removeRedundantAttributes?: boolean;
57
103
  removeOptionalTags?: boolean;
58
- removeUnusedCss?: boolean | ({
59
- tool: 'purgeCSS';
60
- } & Omit<UserDefinedOptions, 'content' | 'css' | 'extractors'>) | {
104
+ removeUnusedCss?: boolean | HtmlnanoPurgeCssOptions | {
105
+ tool?: 'uncss';
61
106
  banner?: boolean;
62
107
  csspath?: string;
63
108
  htmlroot?: string;
@@ -77,7 +122,7 @@ interface HtmlnanoOptions {
77
122
  type HtmlnanoModuleAttrsHandler = (attrs: Record<string, string | boolean | void>, node: PostHTML.Node) => Record<string, string | boolean | void>;
78
123
  type HtmlnanoModuleContentHandler = (content: Array<PostHTMLNodeLike>, node: PostHTML.Node) => MaybeArray<PostHTMLNodeLike>;
79
124
  type HtmlnanoModuleNodeHandler = (node: PostHTMLNodeLike) => PostHTML.Node | string;
80
- type OptionalOptions<T> = T extends boolean | string | Function | number | null | undefined ? T : T extends object ? Partial<T> : T;
125
+ type OptionalOptions<T> = T extends boolean | string | Function | number | null | undefined ? T : T extends Array<infer Item> ? Array<Item> : T extends object ? Partial<T> : T;
81
126
  type HtmlnanoModule<Options = any> = {
82
127
  onAttrs?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleAttrsHandler;
83
128
  onContent?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleContentHandler;
@@ -87,4 +132,6 @@ type HtmlnanoModule<Options = any> = {
87
132
 
88
133
  declare const mod: HtmlnanoModule<Options>;
89
134
 
90
- export { mod as default };
135
+ declare function getInlineCssnanoOptions(cssnanoOptions: Options | undefined): Options | undefined;
136
+
137
+ export { mod as default, getInlineCssnanoOptions };
@@ -8,6 +8,14 @@ const postcssOptions = {
8
8
  // > Set it to CSS file path or to `undefined` to prevent this warning.
9
9
  from: undefined
10
10
  };
11
+ const inlineCssExcludedPlugins = {
12
+ mergeRules: false,
13
+ minifySelectors: false,
14
+ minifyParams: false,
15
+ normalizeCharset: false,
16
+ uniqueSelectors: false,
17
+ normalizeUnicode: false
18
+ };
11
19
  /** Minify CSS with cssnano */ const mod = {
12
20
  async default (tree, _, cssnanoOptions) {
13
21
  const cssnano = await helpers_js.optionalImport('cssnano');
@@ -15,6 +23,9 @@ const postcssOptions = {
15
23
  if (!cssnano || !postcss) {
16
24
  return tree;
17
25
  }
26
+ const processor = createCssProcessor(postcss, cssnano, cssnanoOptions);
27
+ const inlineStyleProcessor = createCssProcessor(postcss, cssnano, getInlineCssnanoOptions(cssnanoOptions));
28
+ const minifiedCssCache = new Map();
18
29
  const promises = [];
19
30
  let p;
20
31
  tree.walk((node)=>{
@@ -23,12 +34,12 @@ const postcssOptions = {
23
34
  return node;
24
35
  }
25
36
  if (helpers_js.isStyleNode(node) && helpers_js.isCssStyleType(node)) {
26
- p = processStyleNode(node, cssnanoOptions, cssnano, postcss);
37
+ p = processStyleNode(node, processor, minifiedCssCache);
27
38
  if (p) {
28
39
  promises.push(p);
29
40
  }
30
41
  } else if (node.attrs && node.attrs.style) {
31
- p = processStyleAttr(node, cssnanoOptions, cssnano, postcss);
42
+ p = processStyleAttr(node, inlineStyleProcessor, minifiedCssCache);
32
43
  if (p) {
33
44
  promises.push(p);
34
45
  }
@@ -38,22 +49,69 @@ const postcssOptions = {
38
49
  return Promise.all(promises).then(()=>tree);
39
50
  }
40
51
  };
41
- function processStyleNode(styleNode, cssnanoOptions, cssnano, postcss) {
52
+ function createCssProcessor(postcss, cssnano, cssnanoOptions) {
53
+ return postcss([
54
+ cssnano(cssnanoOptions)
55
+ ]);
56
+ }
57
+ function getInlineCssnanoOptions(cssnanoOptions) {
58
+ if (!cssnanoOptions || typeof cssnanoOptions !== 'object') {
59
+ return {
60
+ preset: [
61
+ 'default',
62
+ inlineCssExcludedPlugins
63
+ ]
64
+ };
65
+ }
66
+ if (Array.isArray(cssnanoOptions.plugins)) {
67
+ return cssnanoOptions;
68
+ }
69
+ if (!('preset' in cssnanoOptions) || cssnanoOptions.preset === undefined) {
70
+ return {
71
+ ...cssnanoOptions,
72
+ preset: [
73
+ 'default',
74
+ inlineCssExcludedPlugins
75
+ ]
76
+ };
77
+ }
78
+ if (cssnanoOptions.preset === 'default') {
79
+ return {
80
+ ...cssnanoOptions,
81
+ preset: [
82
+ 'default',
83
+ inlineCssExcludedPlugins
84
+ ]
85
+ };
86
+ }
87
+ if (Array.isArray(cssnanoOptions.preset) && cssnanoOptions.preset[0] === 'default') {
88
+ const presetOptions = cssnanoOptions.preset[1];
89
+ return {
90
+ ...cssnanoOptions,
91
+ preset: [
92
+ 'default',
93
+ {
94
+ ...inlineCssExcludedPlugins,
95
+ ...presetOptions && typeof presetOptions === 'object' ? presetOptions : {}
96
+ }
97
+ ]
98
+ };
99
+ }
100
+ return cssnanoOptions;
101
+ }
102
+ function processStyleNode(styleNode, processor, minifiedCssCache) {
42
103
  let css = helpers_js.extractCssFromStyleNode(styleNode);
43
104
  if (!css || css.trim() === '') return;
44
105
  // Improve performance by avoiding calling stripCssCdata again and again
45
106
  const { strippedCss, isCdataWrapped } = helpers_js.stripCssCdata(css);
46
107
  css = strippedCss;
47
- return postcss([
48
- cssnano(cssnanoOptions)
49
- ]).process(css, postcssOptions).then((result)=>{
50
- const minifiedCss = isCdataWrapped ? result.toString() : result.css;
108
+ return processCss(processor, minifiedCssCache, `${isCdataWrapped ? 'style-cdata:' : 'style:'}${css}`, css, isCdataWrapped).then((minifiedCss)=>{
51
109
  styleNode.content = [
52
110
  helpers_js.wrapCssCdata(minifiedCss, isCdataWrapped)
53
111
  ];
54
112
  });
55
113
  }
56
- function processStyleAttr(node, cssnanoOptions, cssnano, postcss) {
114
+ function processStyleAttr(node, processor, minifiedCssCache) {
57
115
  // CSS "color: red;" is invalid. Therefore it should be wrapped inside some selector:
58
116
  // a{color: red;}
59
117
  const wrapperStart = 'a{';
@@ -65,13 +123,22 @@ function processStyleAttr(node, cssnanoOptions, cssnano, postcss) {
65
123
  return;
66
124
  }
67
125
  const wrappedStyle = wrapperStart + (node.attrs.style || '') + wrapperEnd;
68
- return postcss([
69
- cssnano(cssnanoOptions)
70
- ]).process(wrappedStyle, postcssOptions).then((result)=>{
71
- const minifiedCss = result.css;
126
+ return processCss(processor, minifiedCssCache, `attr:${wrappedStyle}`, wrappedStyle).then((minifiedCss)=>{
72
127
  // Remove wrapperStart at the start and wrapperEnd at the end of minifiedCss
73
128
  node.attrs.style = minifiedCss.substring(wrapperStart.length, minifiedCss.length - wrapperEnd.length);
74
129
  });
75
130
  }
131
+ function processCss(processor, minifiedCssCache, cacheKey, css, useToString = false) {
132
+ let minifiedCss = minifiedCssCache.get(cacheKey);
133
+ if (!minifiedCss) {
134
+ minifiedCss = processor.process(css, postcssOptions).then((result)=>useToString ? result.toString() : result.css).catch((error)=>{
135
+ minifiedCssCache.delete(cacheKey);
136
+ throw error;
137
+ });
138
+ minifiedCssCache.set(cacheKey, minifiedCss);
139
+ }
140
+ return minifiedCss;
141
+ }
76
142
 
77
143
  exports.default = mod;
144
+ exports.getInlineCssnanoOptions = getInlineCssnanoOptions;
@@ -6,6 +6,14 @@ const postcssOptions = {
6
6
  // > Set it to CSS file path or to `undefined` to prevent this warning.
7
7
  from: undefined
8
8
  };
9
+ const inlineCssExcludedPlugins = {
10
+ mergeRules: false,
11
+ minifySelectors: false,
12
+ minifyParams: false,
13
+ normalizeCharset: false,
14
+ uniqueSelectors: false,
15
+ normalizeUnicode: false
16
+ };
9
17
  /** Minify CSS with cssnano */ const mod = {
10
18
  async default (tree, _, cssnanoOptions) {
11
19
  const cssnano = await optionalImport('cssnano');
@@ -13,6 +21,9 @@ const postcssOptions = {
13
21
  if (!cssnano || !postcss) {
14
22
  return tree;
15
23
  }
24
+ const processor = createCssProcessor(postcss, cssnano, cssnanoOptions);
25
+ const inlineStyleProcessor = createCssProcessor(postcss, cssnano, getInlineCssnanoOptions(cssnanoOptions));
26
+ const minifiedCssCache = new Map();
16
27
  const promises = [];
17
28
  let p;
18
29
  tree.walk((node)=>{
@@ -21,12 +32,12 @@ const postcssOptions = {
21
32
  return node;
22
33
  }
23
34
  if (isStyleNode(node) && isCssStyleType(node)) {
24
- p = processStyleNode(node, cssnanoOptions, cssnano, postcss);
35
+ p = processStyleNode(node, processor, minifiedCssCache);
25
36
  if (p) {
26
37
  promises.push(p);
27
38
  }
28
39
  } else if (node.attrs && node.attrs.style) {
29
- p = processStyleAttr(node, cssnanoOptions, cssnano, postcss);
40
+ p = processStyleAttr(node, inlineStyleProcessor, minifiedCssCache);
30
41
  if (p) {
31
42
  promises.push(p);
32
43
  }
@@ -36,22 +47,69 @@ const postcssOptions = {
36
47
  return Promise.all(promises).then(()=>tree);
37
48
  }
38
49
  };
39
- function processStyleNode(styleNode, cssnanoOptions, cssnano, postcss) {
50
+ function createCssProcessor(postcss, cssnano, cssnanoOptions) {
51
+ return postcss([
52
+ cssnano(cssnanoOptions)
53
+ ]);
54
+ }
55
+ function getInlineCssnanoOptions(cssnanoOptions) {
56
+ if (!cssnanoOptions || typeof cssnanoOptions !== 'object') {
57
+ return {
58
+ preset: [
59
+ 'default',
60
+ inlineCssExcludedPlugins
61
+ ]
62
+ };
63
+ }
64
+ if (Array.isArray(cssnanoOptions.plugins)) {
65
+ return cssnanoOptions;
66
+ }
67
+ if (!('preset' in cssnanoOptions) || cssnanoOptions.preset === undefined) {
68
+ return {
69
+ ...cssnanoOptions,
70
+ preset: [
71
+ 'default',
72
+ inlineCssExcludedPlugins
73
+ ]
74
+ };
75
+ }
76
+ if (cssnanoOptions.preset === 'default') {
77
+ return {
78
+ ...cssnanoOptions,
79
+ preset: [
80
+ 'default',
81
+ inlineCssExcludedPlugins
82
+ ]
83
+ };
84
+ }
85
+ if (Array.isArray(cssnanoOptions.preset) && cssnanoOptions.preset[0] === 'default') {
86
+ const presetOptions = cssnanoOptions.preset[1];
87
+ return {
88
+ ...cssnanoOptions,
89
+ preset: [
90
+ 'default',
91
+ {
92
+ ...inlineCssExcludedPlugins,
93
+ ...presetOptions && typeof presetOptions === 'object' ? presetOptions : {}
94
+ }
95
+ ]
96
+ };
97
+ }
98
+ return cssnanoOptions;
99
+ }
100
+ function processStyleNode(styleNode, processor, minifiedCssCache) {
40
101
  let css = extractCssFromStyleNode(styleNode);
41
102
  if (!css || css.trim() === '') return;
42
103
  // Improve performance by avoiding calling stripCssCdata again and again
43
104
  const { strippedCss, isCdataWrapped } = stripCssCdata(css);
44
105
  css = strippedCss;
45
- return postcss([
46
- cssnano(cssnanoOptions)
47
- ]).process(css, postcssOptions).then((result)=>{
48
- const minifiedCss = isCdataWrapped ? result.toString() : result.css;
106
+ return processCss(processor, minifiedCssCache, `${isCdataWrapped ? 'style-cdata:' : 'style:'}${css}`, css, isCdataWrapped).then((minifiedCss)=>{
49
107
  styleNode.content = [
50
108
  wrapCssCdata(minifiedCss, isCdataWrapped)
51
109
  ];
52
110
  });
53
111
  }
54
- function processStyleAttr(node, cssnanoOptions, cssnano, postcss) {
112
+ function processStyleAttr(node, processor, minifiedCssCache) {
55
113
  // CSS "color: red;" is invalid. Therefore it should be wrapped inside some selector:
56
114
  // a{color: red;}
57
115
  const wrapperStart = 'a{';
@@ -63,13 +121,21 @@ function processStyleAttr(node, cssnanoOptions, cssnano, postcss) {
63
121
  return;
64
122
  }
65
123
  const wrappedStyle = wrapperStart + (node.attrs.style || '') + wrapperEnd;
66
- return postcss([
67
- cssnano(cssnanoOptions)
68
- ]).process(wrappedStyle, postcssOptions).then((result)=>{
69
- const minifiedCss = result.css;
124
+ return processCss(processor, minifiedCssCache, `attr:${wrappedStyle}`, wrappedStyle).then((minifiedCss)=>{
70
125
  // Remove wrapperStart at the start and wrapperEnd at the end of minifiedCss
71
126
  node.attrs.style = minifiedCss.substring(wrapperStart.length, minifiedCss.length - wrapperEnd.length);
72
127
  });
73
128
  }
129
+ function processCss(processor, minifiedCssCache, cacheKey, css, useToString = false) {
130
+ let minifiedCss = minifiedCssCache.get(cacheKey);
131
+ if (!minifiedCss) {
132
+ minifiedCss = processor.process(css, postcssOptions).then((result)=>useToString ? result.toString() : result.css).catch((error)=>{
133
+ minifiedCssCache.delete(cacheKey);
134
+ throw error;
135
+ });
136
+ minifiedCssCache.set(cacheKey, minifiedCss);
137
+ }
138
+ return minifiedCss;
139
+ }
74
140
 
75
- export { mod as default };
141
+ export { mod as default, getInlineCssnanoOptions };
@@ -1,25 +1,70 @@
1
1
  import PostHTML from 'posthtml';
2
- import { MinifyOptions } from 'terser';
3
- import { Options } from 'cssnano';
4
- import { Config } from 'svgo';
5
- import { UserDefinedOptions } from 'purgecss';
6
2
 
7
- type PostHTMLTreeLike = [PostHTML.Node] & PostHTML.NodeAPI & {
3
+ type PostHTMLNodeLike = PostHTML.Node | string;
4
+ type PostHTMLTreeLike = [PostHTMLNodeLike] & PostHTML.NodeAPI & {
8
5
  options?: {
9
6
  quoteAllAttributes?: boolean | undefined;
10
7
  quoteStyle?: 0 | 1 | 2 | undefined;
11
8
  replaceQuote?: boolean | undefined;
12
9
  } | undefined;
13
10
  render(): string;
14
- render(node: PostHTML.Node | PostHTMLTreeLike, renderOptions?: any): string;
11
+ render(node: PostHTMLNodeLike | PostHTMLTreeLike, renderOptions?: any): string;
15
12
  };
16
13
  type MaybeArray<T> = T | Array<T>;
17
- type PostHTMLNodeLike = PostHTML.Node | string;
18
14
  type HtmlnanoTemplateRule = {
19
15
  tag: string;
20
16
  attrs?: Record<string, string | boolean | void>;
21
17
  };
22
18
  type MinifyHtmlTemplateOptions = boolean | HtmlnanoTemplateRule[];
19
+ type HtmlnanoMinifyCssOptions = object;
20
+ type HtmlnanoMinifyJsOptions = object;
21
+ type HtmlnanoMinifySvgOptions = object;
22
+ type HtmlnanoPurgeCssPattern = string | RegExp;
23
+ type HtmlnanoPurgeCssExtractorResultDetailed = {
24
+ attributes: {
25
+ names: string[];
26
+ values: string[];
27
+ };
28
+ classes: string[];
29
+ ids: string[];
30
+ tags: string[];
31
+ undetermined: string[];
32
+ };
33
+ type HtmlnanoPurgeCssExtractorResult = HtmlnanoPurgeCssExtractorResultDetailed | string[];
34
+ type HtmlnanoPurgeCssDefaultExtractor = (content: string) => HtmlnanoPurgeCssExtractorResult;
35
+ type HtmlnanoPurgeCssSourceMapOptions = {
36
+ absolute?: boolean;
37
+ annotation?: boolean | string;
38
+ from?: string;
39
+ inline?: boolean;
40
+ prev?: boolean | object | string;
41
+ sourcesContent?: boolean;
42
+ to?: string;
43
+ };
44
+ type HtmlnanoPurgeCssSafelist = HtmlnanoPurgeCssPattern[] | {
45
+ standard?: HtmlnanoPurgeCssPattern[];
46
+ deep?: RegExp[];
47
+ greedy?: RegExp[];
48
+ variables?: HtmlnanoPurgeCssPattern[];
49
+ keyframes?: HtmlnanoPurgeCssPattern[];
50
+ };
51
+ interface HtmlnanoPurgeCssOptions {
52
+ tool: 'purgeCSS';
53
+ defaultExtractor?: HtmlnanoPurgeCssDefaultExtractor;
54
+ fontFace?: boolean;
55
+ keyframes?: boolean;
56
+ output?: string;
57
+ rejected?: boolean;
58
+ rejectedCss?: boolean;
59
+ sourceMap?: boolean | HtmlnanoPurgeCssSourceMapOptions;
60
+ stdin?: boolean;
61
+ stdout?: boolean;
62
+ variables?: boolean;
63
+ safelist?: HtmlnanoPurgeCssSafelist;
64
+ blocklist?: HtmlnanoPurgeCssPattern[];
65
+ skippedContentGlobs?: string[];
66
+ dynamicAttributes?: string[];
67
+ }
23
68
  interface HtmlnanoOptions {
24
69
  skipConfigLoading?: boolean;
25
70
  configPath?: string;
@@ -34,16 +79,16 @@ interface HtmlnanoOptions {
34
79
  minifyUrls?: URL | string | false;
35
80
  mergeStyles?: boolean;
36
81
  mergeScripts?: boolean;
37
- minifyCss?: Options | boolean;
82
+ minifyCss?: HtmlnanoMinifyCssOptions | boolean;
38
83
  minifyHtmlTemplate?: MinifyHtmlTemplateOptions;
39
84
  minifyConditionalComments?: boolean;
40
- minifyJs?: MinifyOptions | boolean;
85
+ minifyJs?: HtmlnanoMinifyJsOptions | boolean;
41
86
  minifyJson?: boolean;
42
87
  minifyAttributes?: boolean | {
43
88
  metaContent?: boolean;
44
89
  redundantWhitespaces?: 'safe' | 'agressive' | false;
45
90
  };
46
- minifySvg?: Config | boolean;
91
+ minifySvg?: HtmlnanoMinifySvgOptions | boolean;
47
92
  normalizeAttributeValues?: boolean;
48
93
  removeAttributeQuotes?: boolean | {
49
94
  force?: boolean;
@@ -55,9 +100,8 @@ interface HtmlnanoOptions {
55
100
  };
56
101
  removeRedundantAttributes?: boolean;
57
102
  removeOptionalTags?: boolean;
58
- removeUnusedCss?: boolean | ({
59
- tool: 'purgeCSS';
60
- } & Omit<UserDefinedOptions, 'content' | 'css' | 'extractors'>) | {
103
+ removeUnusedCss?: boolean | HtmlnanoPurgeCssOptions | {
104
+ tool?: 'uncss';
61
105
  banner?: boolean;
62
106
  csspath?: string;
63
107
  htmlroot?: string;
@@ -77,7 +121,7 @@ interface HtmlnanoOptions {
77
121
  type HtmlnanoModuleAttrsHandler = (attrs: Record<string, string | boolean | void>, node: PostHTML.Node) => Record<string, string | boolean | void>;
78
122
  type HtmlnanoModuleContentHandler = (content: Array<PostHTMLNodeLike>, node: PostHTML.Node) => MaybeArray<PostHTMLNodeLike>;
79
123
  type HtmlnanoModuleNodeHandler = (node: PostHTMLNodeLike) => PostHTML.Node | string;
80
- type OptionalOptions<T> = T extends boolean | string | Function | number | null | undefined ? T : T extends object ? Partial<T> : T;
124
+ type OptionalOptions<T> = T extends boolean | string | Function | number | null | undefined ? T : T extends Array<infer Item> ? Array<Item> : T extends object ? Partial<T> : T;
81
125
  type HtmlnanoModule<Options = any> = {
82
126
  onAttrs?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleAttrsHandler;
83
127
  onContent?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleContentHandler;