htmlnano 2.1.3 → 2.1.5
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 +16 -15
- package/dist/_modules/minifyCss.js +9 -2
- package/dist/_modules/minifyCss.mjs +9 -2
- package/dist/_modules/minifyJs.js +5 -1
- package/dist/_modules/minifyJs.mjs +5 -1
- package/dist/helpers.d.ts +16 -0
- package/dist/index.d.ts +78 -0
- package/dist/index.js +63 -10
- package/dist/index.mjs +64 -10
- package/dist/presets/ampSafe.d.ts +47 -0
- package/dist/presets/max.d.ts +47 -0
- package/dist/presets/safe.d.ts +47 -0
- package/package.json +11 -5
package/README.md
CHANGED
|
@@ -7,21 +7,22 @@ Modular HTML minifier, built on top of the [PostHTML](https://github.com/posthtm
|
|
|
7
7
|
## Benchmarks
|
|
8
8
|
|
|
9
9
|
[html-minifier-terser]: https://www.npmjs.com/package/html-minifier-terser/v/7.2.0
|
|
10
|
-
[html-minifier-next]: https://www.npmjs.com/package/html-minifier-next/v/1.
|
|
11
|
-
[htmlnano]: https://www.npmjs.com/package/htmlnano/v/2.1.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
| [
|
|
18
|
-
| [
|
|
19
|
-
| [
|
|
20
|
-
| [
|
|
21
|
-
| [
|
|
22
|
-
| [
|
|
23
|
-
| [
|
|
24
|
-
|
|
|
10
|
+
[html-minifier-next]: https://www.npmjs.com/package/html-minifier-next/v/1.4.0
|
|
11
|
+
[htmlnano]: https://www.npmjs.com/package/htmlnano/v/2.1.3
|
|
12
|
+
[minify]: https://www.npmjs.com/package/@tdewolff/minify/v/2.24.2
|
|
13
|
+
[minify-html]: https://www.npmjs.com/package/@minify-html/node/v/0.16.4
|
|
14
|
+
|
|
15
|
+
| Website | Source (KB) | [html-minifier-terser] | [html-minifier-next] | [htmlnano] | [minify] | [minify-html] |
|
|
16
|
+
| ----------------------------------------------------------- | ----------: | ---------------------: | -------------------: | ---------: | -------: | ------------: |
|
|
17
|
+
| [stackoverflow.blog](https://stackoverflow.blog/) | 166 | 3.3% | 3.3% | 8.3% | 4.6% | 4.0% |
|
|
18
|
+
| [github.com](https://github.com/) | 541 | 3.7% | 3.7% | 18.1% | 7.9% | 6.2% |
|
|
19
|
+
| [en.wikipedia.org](https://en.wikipedia.org/wiki/Main_Page) | 220 | 4.6% | 4.6% | 4.9% | 6.2% | 2.9% |
|
|
20
|
+
| [npmjs.com](https://www.npmjs.com/package/eslint) | 460 | 0.5% | 0.5% | 0.9% | 3.6% | 0.7% |
|
|
21
|
+
| [tc39.es](https://tc39.es/ecma262/) | 7198 | 8.5% | 8.5% | 8.7% | 9.5% | 9.1% |
|
|
22
|
+
| [apple.com](https://www.apple.com/) | 190 | 7.6% | 7.6% | 12.1% | 10.5% | 8.1% |
|
|
23
|
+
| [w3.org](https://www.w3.org/) | 49 | 18.9% | 18.9% | 23.0% | 24.1% | 19.9% |
|
|
24
|
+
| [weather.com](https://weather.com) | 1770 | 0.2% | 0.2% | 12.1% | 11.9% | 0.6% |
|
|
25
|
+
| **Avg. minify rate** | | **5.9%** | **5.9%** | **11.0%** | **9.8%** | **6.4%** |
|
|
25
26
|
|
|
26
27
|
Latest benchmarks: https://github.com/maltsev/html-minifiers-benchmark (updated daily).
|
|
27
28
|
|
|
@@ -16,15 +16,22 @@ const postcssOptions = {
|
|
|
16
16
|
return tree;
|
|
17
17
|
}
|
|
18
18
|
const promises = [];
|
|
19
|
+
let p;
|
|
19
20
|
tree.walk((node)=>{
|
|
20
21
|
// Skip SRI, reasons are documented in "minifyJs" module
|
|
21
22
|
if (node.attrs && 'integrity' in node.attrs) {
|
|
22
23
|
return node;
|
|
23
24
|
}
|
|
24
25
|
if (helpers_js.isStyleNode(node)) {
|
|
25
|
-
|
|
26
|
+
p = processStyleNode(node, cssnanoOptions, cssnano, postcss);
|
|
27
|
+
if (p) {
|
|
28
|
+
promises.push(p);
|
|
29
|
+
}
|
|
26
30
|
} else if (node.attrs && node.attrs.style) {
|
|
27
|
-
|
|
31
|
+
p = processStyleAttr(node, cssnanoOptions, cssnano, postcss);
|
|
32
|
+
if (p) {
|
|
33
|
+
promises.push(p);
|
|
34
|
+
}
|
|
28
35
|
}
|
|
29
36
|
return node;
|
|
30
37
|
});
|
|
@@ -14,15 +14,22 @@ const postcssOptions = {
|
|
|
14
14
|
return tree;
|
|
15
15
|
}
|
|
16
16
|
const promises = [];
|
|
17
|
+
let p;
|
|
17
18
|
tree.walk((node)=>{
|
|
18
19
|
// Skip SRI, reasons are documented in "minifyJs" module
|
|
19
20
|
if (node.attrs && 'integrity' in node.attrs) {
|
|
20
21
|
return node;
|
|
21
22
|
}
|
|
22
23
|
if (isStyleNode(node)) {
|
|
23
|
-
|
|
24
|
+
p = processStyleNode(node, cssnanoOptions, cssnano, postcss);
|
|
25
|
+
if (p) {
|
|
26
|
+
promises.push(p);
|
|
27
|
+
}
|
|
24
28
|
} else if (node.attrs && node.attrs.style) {
|
|
25
|
-
|
|
29
|
+
p = processStyleAttr(node, cssnanoOptions, cssnano, postcss);
|
|
30
|
+
if (p) {
|
|
31
|
+
promises.push(p);
|
|
32
|
+
}
|
|
26
33
|
}
|
|
27
34
|
return node;
|
|
28
35
|
});
|
|
@@ -8,6 +8,7 @@ var removeRedundantAttributes_js = require('./removeRedundantAttributes.js');
|
|
|
8
8
|
const terser = await helpers_js.optionalImport('terser');
|
|
9
9
|
if (!terser) return tree;
|
|
10
10
|
const promises = [];
|
|
11
|
+
let p;
|
|
11
12
|
tree.walk((node)=>{
|
|
12
13
|
const nodeAttrs = node.attrs || {};
|
|
13
14
|
/**
|
|
@@ -27,7 +28,10 @@ var removeRedundantAttributes_js = require('./removeRedundantAttributes.js');
|
|
|
27
28
|
if (node.tag && node.tag === 'script') {
|
|
28
29
|
const mimeType = nodeAttrs.type || 'text/javascript';
|
|
29
30
|
if (removeRedundantAttributes_js.redundantScriptTypes.has(mimeType) || mimeType === 'module') {
|
|
30
|
-
|
|
31
|
+
p = processScriptNode(node, terserOptions, terser);
|
|
32
|
+
if (p) {
|
|
33
|
+
promises.push(p);
|
|
34
|
+
}
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
if (node.attrs) {
|
|
@@ -6,6 +6,7 @@ import { redundantScriptTypes } from './removeRedundantAttributes.mjs';
|
|
|
6
6
|
const terser = await optionalImport('terser');
|
|
7
7
|
if (!terser) return tree;
|
|
8
8
|
const promises = [];
|
|
9
|
+
let p;
|
|
9
10
|
tree.walk((node)=>{
|
|
10
11
|
const nodeAttrs = node.attrs || {};
|
|
11
12
|
/**
|
|
@@ -25,7 +26,10 @@ import { redundantScriptTypes } from './removeRedundantAttributes.mjs';
|
|
|
25
26
|
if (node.tag && node.tag === 'script') {
|
|
26
27
|
const mimeType = nodeAttrs.type || 'text/javascript';
|
|
27
28
|
if (redundantScriptTypes.has(mimeType) || mimeType === 'module') {
|
|
28
|
-
|
|
29
|
+
p = processScriptNode(node, terserOptions, terser);
|
|
30
|
+
if (p) {
|
|
31
|
+
promises.push(p);
|
|
32
|
+
}
|
|
29
33
|
}
|
|
30
34
|
}
|
|
31
35
|
if (node.attrs) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import PostHTML from 'posthtml';
|
|
2
|
+
|
|
3
|
+
type PostHTMLNodeLike = PostHTML.Node | string;
|
|
4
|
+
|
|
5
|
+
declare function isAmpBoilerplate(node: PostHTML.Node): boolean;
|
|
6
|
+
declare function isComment(content: PostHTMLNodeLike | null): boolean;
|
|
7
|
+
declare function isConditionalComment(content: string): boolean;
|
|
8
|
+
declare function isStyleNode(node: PostHTML.Node): boolean | undefined;
|
|
9
|
+
declare function extractCssFromStyleNode(node: PostHTML.Node): string | undefined;
|
|
10
|
+
declare function isEventHandler(attributeName: string): boolean | "";
|
|
11
|
+
declare function extractTextContentFromNode(node: PostHTML.Node): string;
|
|
12
|
+
declare function optionalImport<Module = unknown, Default = Module>(moduleName: string): Promise<(Module & {
|
|
13
|
+
default?: Default;
|
|
14
|
+
}) | NonNullable<Default> | null>;
|
|
15
|
+
|
|
16
|
+
export { extractCssFromStyleNode, extractTextContentFromNode, isAmpBoilerplate, isComment, isConditionalComment, isEventHandler, isStyleNode, optionalImport };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import PostHTML from 'posthtml';
|
|
2
|
+
import { MinifyOptions } from 'terser';
|
|
3
|
+
import { Options } from 'cssnano';
|
|
4
|
+
import { Config } from 'svgo';
|
|
5
|
+
|
|
6
|
+
type PostHTMLTreeLike = [PostHTML.Node] & PostHTML.NodeAPI & {
|
|
7
|
+
options?: {
|
|
8
|
+
quoteAllAttributes?: boolean | undefined;
|
|
9
|
+
} | undefined;
|
|
10
|
+
render(): string;
|
|
11
|
+
render(node: PostHTML.Node | PostHTMLTreeLike, renderOptions?: any): string;
|
|
12
|
+
};
|
|
13
|
+
type MaybeArray<T> = T | Array<T>;
|
|
14
|
+
type PostHTMLNodeLike = PostHTML.Node | string;
|
|
15
|
+
interface HtmlnanoOptions {
|
|
16
|
+
skipConfigLoading?: boolean;
|
|
17
|
+
skipInternalWarnings?: boolean;
|
|
18
|
+
collapseAttributeWhitespace?: boolean;
|
|
19
|
+
collapseBooleanAttributes?: {
|
|
20
|
+
amphtml?: boolean;
|
|
21
|
+
};
|
|
22
|
+
collapseWhitespace?: 'conservative' | 'all' | 'aggressive';
|
|
23
|
+
custom?: MaybeArray<(tree: PostHTMLTreeLike, options?: any) => (PostHTML.Node | PostHTMLTreeLike)>;
|
|
24
|
+
deduplicateAttributeValues?: boolean;
|
|
25
|
+
minifyUrls?: URL | string | false;
|
|
26
|
+
mergeStyles?: boolean;
|
|
27
|
+
mergeScripts?: boolean;
|
|
28
|
+
minifyCss?: Options | boolean;
|
|
29
|
+
minifyConditionalComments?: boolean;
|
|
30
|
+
minifyJs?: MinifyOptions | boolean;
|
|
31
|
+
minifyJson?: boolean;
|
|
32
|
+
minifySvg?: Config | boolean;
|
|
33
|
+
normalizeAttributeValues?: boolean;
|
|
34
|
+
removeAttributeQuotes?: boolean;
|
|
35
|
+
removeComments?: boolean | 'safe' | 'all' | RegExp | ((comment: string) => boolean);
|
|
36
|
+
removeEmptyAttributes?: boolean;
|
|
37
|
+
removeRedundantAttributes?: boolean;
|
|
38
|
+
removeOptionalTags?: boolean;
|
|
39
|
+
removeUnusedCss?: boolean;
|
|
40
|
+
sortAttributes?: boolean | 'alphabetical' | 'frequency';
|
|
41
|
+
sortAttributesWithLists?: boolean | 'alphabetical' | 'frequency';
|
|
42
|
+
}
|
|
43
|
+
interface HtmlnanoPreset extends Omit<HtmlnanoOptions, 'skipConfigLoading'> {
|
|
44
|
+
}
|
|
45
|
+
type HtmlnanoPredefinedPreset = 'safe' | 'ampSafe' | 'max';
|
|
46
|
+
type HtmlnanoPredefinedPresets = Record<HtmlnanoPredefinedPreset, HtmlnanoPreset>;
|
|
47
|
+
type HtmlnanoOptionsConfigFile = Omit<HtmlnanoOptions, 'skipConfigLoading'> & {
|
|
48
|
+
preset?: HtmlnanoPredefinedPreset;
|
|
49
|
+
};
|
|
50
|
+
type HtmlnanoModuleAttrsHandler = (attrs: Record<string, string | boolean | void>, node: PostHTML.Node) => Record<string, string | boolean | void>;
|
|
51
|
+
type HtmlnanoModuleContentHandler = (content: Array<PostHTMLNodeLike>, node: PostHTML.Node) => MaybeArray<PostHTMLNodeLike>;
|
|
52
|
+
type HtmlnanoModuleNodeHandler = (node: PostHTMLNodeLike) => PostHTML.Node | string;
|
|
53
|
+
type OptionalOptions<T> = T extends boolean | string | Function | number | null | undefined ? T : T extends object ? Partial<T> : T;
|
|
54
|
+
type HtmlnanoModule<Options = any> = {
|
|
55
|
+
onAttrs?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleAttrsHandler;
|
|
56
|
+
onContent?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleContentHandler;
|
|
57
|
+
onNode?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleNodeHandler;
|
|
58
|
+
default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
declare function loadConfig(options?: HtmlnanoOptions, preset?: HtmlnanoPreset, configPath?: string): [Partial<HtmlnanoOptions>, HtmlnanoPreset];
|
|
62
|
+
declare const htmlnano: ((optionsRun?: HtmlnanoOptions, presetRun?: HtmlnanoPreset) => PostHTML.Plugin<never>) & {
|
|
63
|
+
presets: HtmlnanoPredefinedPresets;
|
|
64
|
+
getRequiredOptionalDependencies: typeof getRequiredOptionalDependencies;
|
|
65
|
+
process: typeof process;
|
|
66
|
+
htmlMinimizerWebpackPluginMinify: typeof htmlMinimizerWebpackPluginMinify;
|
|
67
|
+
loadConfig: typeof loadConfig;
|
|
68
|
+
};
|
|
69
|
+
declare function getRequiredOptionalDependencies(optionsRun: HtmlnanoOptions, presetRun: HtmlnanoPreset): string[];
|
|
70
|
+
declare function process(html: string, options?: HtmlnanoOptions, preset?: HtmlnanoPreset, postHtmlOptions?: PostHTML.Options): Promise<PostHTML.Result<unknown>>;
|
|
71
|
+
declare function htmlMinimizerWebpackPluginMinify(input: {
|
|
72
|
+
[file: string]: string;
|
|
73
|
+
}, minimizerOptions?: HtmlnanoOptions): Promise<{
|
|
74
|
+
code: string;
|
|
75
|
+
}>;
|
|
76
|
+
|
|
77
|
+
export { htmlnano as default, getRequiredOptionalDependencies, htmlMinimizerWebpackPluginMinify, loadConfig, process };
|
|
78
|
+
export type { HtmlnanoModule, HtmlnanoModuleAttrsHandler, HtmlnanoModuleContentHandler, HtmlnanoModuleNodeHandler, HtmlnanoOptions, HtmlnanoOptionsConfigFile, HtmlnanoPredefinedPreset, HtmlnanoPredefinedPresets, HtmlnanoPreset, PostHTMLNodeLike, PostHTMLTreeLike };
|
package/dist/index.js
CHANGED
|
@@ -59,7 +59,60 @@ const optionalDependencies = {
|
|
|
59
59
|
'svgo'
|
|
60
60
|
]
|
|
61
61
|
};
|
|
62
|
-
|
|
62
|
+
/**
|
|
63
|
+
* And the old mixing named export and default export again.
|
|
64
|
+
*
|
|
65
|
+
* TL; DR: our bundler has bundled our mixed default/named export module into a "exports" object,
|
|
66
|
+
* and when dynamically importing a CommonJS module using "import" instead of "require", Node.js wraps
|
|
67
|
+
* another layer of default around the "exports" object.
|
|
68
|
+
*
|
|
69
|
+
* The longer version:
|
|
70
|
+
*
|
|
71
|
+
* The bundler we are using outputs:
|
|
72
|
+
*
|
|
73
|
+
* ESM: export { [named], xxx as default }
|
|
74
|
+
* CJS: exports.default = xxx; exports.[named] = ...; exports.__esModule = true;
|
|
75
|
+
*
|
|
76
|
+
* With ESM, the Module object looks like this:
|
|
77
|
+
*
|
|
78
|
+
* ```js
|
|
79
|
+
* Module {
|
|
80
|
+
* default: xxx,
|
|
81
|
+
* [named]: ...,
|
|
82
|
+
* }
|
|
83
|
+
* ```
|
|
84
|
+
*
|
|
85
|
+
* With CJS, Node.js handles dynamic import differently. Node.js doesn't respect `__esModule`,
|
|
86
|
+
* and will wrongly treat a CommonJS module as ESM, i.e. assign the "exports" object on its
|
|
87
|
+
* own "default" on the "Module" object.
|
|
88
|
+
*
|
|
89
|
+
* Now we have:
|
|
90
|
+
*
|
|
91
|
+
* ```js
|
|
92
|
+
* Module {
|
|
93
|
+
* // this is actually the "exports" inside among "exports.__esModule", "exports.[named]", and "exports.default"
|
|
94
|
+
* default: {
|
|
95
|
+
* __esModule: true,
|
|
96
|
+
* // This is the actual "exports.default"
|
|
97
|
+
* default: xxx
|
|
98
|
+
* }
|
|
99
|
+
* }
|
|
100
|
+
* ```
|
|
101
|
+
*/ const interop = (imported)=>imported.then((mod)=>{
|
|
102
|
+
let htmlnanoModule;
|
|
103
|
+
while('default' in mod){
|
|
104
|
+
htmlnanoModule = mod;
|
|
105
|
+
mod = mod.default;
|
|
106
|
+
// If we find any htmlnano module hook methods, we know this object is a htmlnano module, return directly
|
|
107
|
+
if ('onAttrs' in mod || 'onContent' in mod || 'onNode' in mod) {
|
|
108
|
+
return mod;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (htmlnanoModule && typeof htmlnanoModule.default === 'function') {
|
|
112
|
+
return htmlnanoModule;
|
|
113
|
+
}
|
|
114
|
+
throw new TypeError('The imported module is not a valid htmlnano module');
|
|
115
|
+
});
|
|
63
116
|
const modules = {
|
|
64
117
|
collapseAttributeWhitespace: ()=>interop(import('./_modules/collapseAttributeWhitespace.js')),
|
|
65
118
|
collapseBooleanAttributes: ()=>interop(import('./_modules/collapseBooleanAttributes.js')),
|
|
@@ -85,7 +138,7 @@ const modules = {
|
|
|
85
138
|
sortAttributes: ()=>interop(import('./_modules/sortAttributes.js')),
|
|
86
139
|
sortAttributesWithLists: ()=>interop(import('./_modules/sortAttributesWithLists.js'))
|
|
87
140
|
};
|
|
88
|
-
function htmlnano(optionsRun = {}, presetRun) {
|
|
141
|
+
const htmlnano = Object.assign(function htmlnano(optionsRun = {}, presetRun) {
|
|
89
142
|
// eslint-disable-next-line prefer-const -- re-assign options
|
|
90
143
|
let [options, preset] = loadConfig(optionsRun, presetRun);
|
|
91
144
|
const minifier = async (_tree)=>{
|
|
@@ -180,7 +233,13 @@ function htmlnano(optionsRun = {}, presetRun) {
|
|
|
180
233
|
});
|
|
181
234
|
};
|
|
182
235
|
return minifier;
|
|
183
|
-
}
|
|
236
|
+
}, {
|
|
237
|
+
presets,
|
|
238
|
+
getRequiredOptionalDependencies,
|
|
239
|
+
process,
|
|
240
|
+
htmlMinimizerWebpackPluginMinify,
|
|
241
|
+
loadConfig
|
|
242
|
+
});
|
|
184
243
|
function getRequiredOptionalDependencies(optionsRun, presetRun) {
|
|
185
244
|
const [options] = loadConfig(optionsRun, presetRun);
|
|
186
245
|
return Array.from(Object.keys(options).reduce((acc, moduleName)=>{
|
|
@@ -200,17 +259,12 @@ function process(html, options, preset, postHtmlOptions) {
|
|
|
200
259
|
// https://github.com/webpack-contrib/html-minimizer-webpack-plugin/blob/faca00f2219514bc671c5942685721f0b5dbaa70/src/utils.js#L74
|
|
201
260
|
function htmlMinimizerWebpackPluginMinify(input, minimizerOptions) {
|
|
202
261
|
const [[, code]] = Object.entries(input);
|
|
203
|
-
return
|
|
262
|
+
return process(code, minimizerOptions, presets.safe).then((result)=>{
|
|
204
263
|
return {
|
|
205
264
|
code: result.html
|
|
206
265
|
};
|
|
207
266
|
});
|
|
208
267
|
}
|
|
209
|
-
htmlnano.presets = presets;
|
|
210
|
-
htmlnano.getRequiredOptionalDependencies = getRequiredOptionalDependencies;
|
|
211
|
-
htmlnano.process = process;
|
|
212
|
-
htmlnano.htmlMinimizerWebpackPluginMinify = htmlMinimizerWebpackPluginMinify;
|
|
213
|
-
htmlnano.loadConfig = loadConfig;
|
|
214
268
|
if (typeof module !== 'undefined') {
|
|
215
269
|
module.exports = htmlnano;
|
|
216
270
|
}
|
|
@@ -218,6 +272,5 @@ if (typeof module !== 'undefined') {
|
|
|
218
272
|
exports.default = htmlnano;
|
|
219
273
|
exports.getRequiredOptionalDependencies = getRequiredOptionalDependencies;
|
|
220
274
|
exports.htmlMinimizerWebpackPluginMinify = htmlMinimizerWebpackPluginMinify;
|
|
221
|
-
exports.htmlnano = htmlnano;
|
|
222
275
|
exports.loadConfig = loadConfig;
|
|
223
276
|
exports.process = process;
|
package/dist/index.mjs
CHANGED
|
@@ -50,7 +50,60 @@ const optionalDependencies = {
|
|
|
50
50
|
'svgo'
|
|
51
51
|
]
|
|
52
52
|
};
|
|
53
|
-
|
|
53
|
+
/**
|
|
54
|
+
* And the old mixing named export and default export again.
|
|
55
|
+
*
|
|
56
|
+
* TL; DR: our bundler has bundled our mixed default/named export module into a "exports" object,
|
|
57
|
+
* and when dynamically importing a CommonJS module using "import" instead of "require", Node.js wraps
|
|
58
|
+
* another layer of default around the "exports" object.
|
|
59
|
+
*
|
|
60
|
+
* The longer version:
|
|
61
|
+
*
|
|
62
|
+
* The bundler we are using outputs:
|
|
63
|
+
*
|
|
64
|
+
* ESM: export { [named], xxx as default }
|
|
65
|
+
* CJS: exports.default = xxx; exports.[named] = ...; exports.__esModule = true;
|
|
66
|
+
*
|
|
67
|
+
* With ESM, the Module object looks like this:
|
|
68
|
+
*
|
|
69
|
+
* ```js
|
|
70
|
+
* Module {
|
|
71
|
+
* default: xxx,
|
|
72
|
+
* [named]: ...,
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* With CJS, Node.js handles dynamic import differently. Node.js doesn't respect `__esModule`,
|
|
77
|
+
* and will wrongly treat a CommonJS module as ESM, i.e. assign the "exports" object on its
|
|
78
|
+
* own "default" on the "Module" object.
|
|
79
|
+
*
|
|
80
|
+
* Now we have:
|
|
81
|
+
*
|
|
82
|
+
* ```js
|
|
83
|
+
* Module {
|
|
84
|
+
* // this is actually the "exports" inside among "exports.__esModule", "exports.[named]", and "exports.default"
|
|
85
|
+
* default: {
|
|
86
|
+
* __esModule: true,
|
|
87
|
+
* // This is the actual "exports.default"
|
|
88
|
+
* default: xxx
|
|
89
|
+
* }
|
|
90
|
+
* }
|
|
91
|
+
* ```
|
|
92
|
+
*/ const interop = (imported)=>imported.then((mod)=>{
|
|
93
|
+
let htmlnanoModule;
|
|
94
|
+
while('default' in mod){
|
|
95
|
+
htmlnanoModule = mod;
|
|
96
|
+
mod = mod.default;
|
|
97
|
+
// If we find any htmlnano module hook methods, we know this object is a htmlnano module, return directly
|
|
98
|
+
if ('onAttrs' in mod || 'onContent' in mod || 'onNode' in mod) {
|
|
99
|
+
return mod;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (htmlnanoModule && typeof htmlnanoModule.default === 'function') {
|
|
103
|
+
return htmlnanoModule;
|
|
104
|
+
}
|
|
105
|
+
throw new TypeError('The imported module is not a valid htmlnano module');
|
|
106
|
+
});
|
|
54
107
|
const modules = {
|
|
55
108
|
collapseAttributeWhitespace: ()=>interop(import('./_modules/collapseAttributeWhitespace.mjs')),
|
|
56
109
|
collapseBooleanAttributes: ()=>interop(import('./_modules/collapseBooleanAttributes.mjs')),
|
|
@@ -76,7 +129,7 @@ const modules = {
|
|
|
76
129
|
sortAttributes: ()=>interop(import('./_modules/sortAttributes.mjs')),
|
|
77
130
|
sortAttributesWithLists: ()=>interop(import('./_modules/sortAttributesWithLists.mjs'))
|
|
78
131
|
};
|
|
79
|
-
function htmlnano(optionsRun = {}, presetRun) {
|
|
132
|
+
const htmlnano = Object.assign(function htmlnano(optionsRun = {}, presetRun) {
|
|
80
133
|
// eslint-disable-next-line prefer-const -- re-assign options
|
|
81
134
|
let [options, preset] = loadConfig(optionsRun, presetRun);
|
|
82
135
|
const minifier = async (_tree)=>{
|
|
@@ -171,7 +224,13 @@ function htmlnano(optionsRun = {}, presetRun) {
|
|
|
171
224
|
});
|
|
172
225
|
};
|
|
173
226
|
return minifier;
|
|
174
|
-
}
|
|
227
|
+
}, {
|
|
228
|
+
presets,
|
|
229
|
+
getRequiredOptionalDependencies,
|
|
230
|
+
process,
|
|
231
|
+
htmlMinimizerWebpackPluginMinify,
|
|
232
|
+
loadConfig
|
|
233
|
+
});
|
|
175
234
|
function getRequiredOptionalDependencies(optionsRun, presetRun) {
|
|
176
235
|
const [options] = loadConfig(optionsRun, presetRun);
|
|
177
236
|
return Array.from(Object.keys(options).reduce((acc, moduleName)=>{
|
|
@@ -191,19 +250,14 @@ function process(html, options, preset, postHtmlOptions) {
|
|
|
191
250
|
// https://github.com/webpack-contrib/html-minimizer-webpack-plugin/blob/faca00f2219514bc671c5942685721f0b5dbaa70/src/utils.js#L74
|
|
192
251
|
function htmlMinimizerWebpackPluginMinify(input, minimizerOptions) {
|
|
193
252
|
const [[, code]] = Object.entries(input);
|
|
194
|
-
return
|
|
253
|
+
return process(code, minimizerOptions, presets.safe).then((result)=>{
|
|
195
254
|
return {
|
|
196
255
|
code: result.html
|
|
197
256
|
};
|
|
198
257
|
});
|
|
199
258
|
}
|
|
200
|
-
htmlnano.presets = presets;
|
|
201
|
-
htmlnano.getRequiredOptionalDependencies = getRequiredOptionalDependencies;
|
|
202
|
-
htmlnano.process = process;
|
|
203
|
-
htmlnano.htmlMinimizerWebpackPluginMinify = htmlMinimizerWebpackPluginMinify;
|
|
204
|
-
htmlnano.loadConfig = loadConfig;
|
|
205
259
|
if (typeof module !== 'undefined') {
|
|
206
260
|
module.exports = htmlnano;
|
|
207
261
|
}
|
|
208
262
|
|
|
209
|
-
export { htmlnano as default, getRequiredOptionalDependencies, htmlMinimizerWebpackPluginMinify,
|
|
263
|
+
export { htmlnano as default, getRequiredOptionalDependencies, htmlMinimizerWebpackPluginMinify, loadConfig, process };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import PostHTML from 'posthtml';
|
|
2
|
+
import { MinifyOptions } from 'terser';
|
|
3
|
+
import { Options } from 'cssnano';
|
|
4
|
+
import { Config } from 'svgo';
|
|
5
|
+
|
|
6
|
+
type PostHTMLTreeLike = [PostHTML.Node] & PostHTML.NodeAPI & {
|
|
7
|
+
options?: {
|
|
8
|
+
quoteAllAttributes?: boolean | undefined;
|
|
9
|
+
} | undefined;
|
|
10
|
+
render(): string;
|
|
11
|
+
render(node: PostHTML.Node | PostHTMLTreeLike, renderOptions?: any): string;
|
|
12
|
+
};
|
|
13
|
+
type MaybeArray<T> = T | Array<T>;
|
|
14
|
+
interface HtmlnanoOptions {
|
|
15
|
+
skipConfigLoading?: boolean;
|
|
16
|
+
skipInternalWarnings?: boolean;
|
|
17
|
+
collapseAttributeWhitespace?: boolean;
|
|
18
|
+
collapseBooleanAttributes?: {
|
|
19
|
+
amphtml?: boolean;
|
|
20
|
+
};
|
|
21
|
+
collapseWhitespace?: 'conservative' | 'all' | 'aggressive';
|
|
22
|
+
custom?: MaybeArray<(tree: PostHTMLTreeLike, options?: any) => (PostHTML.Node | PostHTMLTreeLike)>;
|
|
23
|
+
deduplicateAttributeValues?: boolean;
|
|
24
|
+
minifyUrls?: URL | string | false;
|
|
25
|
+
mergeStyles?: boolean;
|
|
26
|
+
mergeScripts?: boolean;
|
|
27
|
+
minifyCss?: Options | boolean;
|
|
28
|
+
minifyConditionalComments?: boolean;
|
|
29
|
+
minifyJs?: MinifyOptions | boolean;
|
|
30
|
+
minifyJson?: boolean;
|
|
31
|
+
minifySvg?: Config | boolean;
|
|
32
|
+
normalizeAttributeValues?: boolean;
|
|
33
|
+
removeAttributeQuotes?: boolean;
|
|
34
|
+
removeComments?: boolean | 'safe' | 'all' | RegExp | ((comment: string) => boolean);
|
|
35
|
+
removeEmptyAttributes?: boolean;
|
|
36
|
+
removeRedundantAttributes?: boolean;
|
|
37
|
+
removeOptionalTags?: boolean;
|
|
38
|
+
removeUnusedCss?: boolean;
|
|
39
|
+
sortAttributes?: boolean | 'alphabetical' | 'frequency';
|
|
40
|
+
sortAttributesWithLists?: boolean | 'alphabetical' | 'frequency';
|
|
41
|
+
}
|
|
42
|
+
interface HtmlnanoPreset extends Omit<HtmlnanoOptions, 'skipConfigLoading'> {
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
declare const _default: HtmlnanoPreset;
|
|
46
|
+
|
|
47
|
+
export { _default as default };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import PostHTML from 'posthtml';
|
|
2
|
+
import { MinifyOptions } from 'terser';
|
|
3
|
+
import { Options } from 'cssnano';
|
|
4
|
+
import { Config } from 'svgo';
|
|
5
|
+
|
|
6
|
+
type PostHTMLTreeLike = [PostHTML.Node] & PostHTML.NodeAPI & {
|
|
7
|
+
options?: {
|
|
8
|
+
quoteAllAttributes?: boolean | undefined;
|
|
9
|
+
} | undefined;
|
|
10
|
+
render(): string;
|
|
11
|
+
render(node: PostHTML.Node | PostHTMLTreeLike, renderOptions?: any): string;
|
|
12
|
+
};
|
|
13
|
+
type MaybeArray<T> = T | Array<T>;
|
|
14
|
+
interface HtmlnanoOptions {
|
|
15
|
+
skipConfigLoading?: boolean;
|
|
16
|
+
skipInternalWarnings?: boolean;
|
|
17
|
+
collapseAttributeWhitespace?: boolean;
|
|
18
|
+
collapseBooleanAttributes?: {
|
|
19
|
+
amphtml?: boolean;
|
|
20
|
+
};
|
|
21
|
+
collapseWhitespace?: 'conservative' | 'all' | 'aggressive';
|
|
22
|
+
custom?: MaybeArray<(tree: PostHTMLTreeLike, options?: any) => (PostHTML.Node | PostHTMLTreeLike)>;
|
|
23
|
+
deduplicateAttributeValues?: boolean;
|
|
24
|
+
minifyUrls?: URL | string | false;
|
|
25
|
+
mergeStyles?: boolean;
|
|
26
|
+
mergeScripts?: boolean;
|
|
27
|
+
minifyCss?: Options | boolean;
|
|
28
|
+
minifyConditionalComments?: boolean;
|
|
29
|
+
minifyJs?: MinifyOptions | boolean;
|
|
30
|
+
minifyJson?: boolean;
|
|
31
|
+
minifySvg?: Config | boolean;
|
|
32
|
+
normalizeAttributeValues?: boolean;
|
|
33
|
+
removeAttributeQuotes?: boolean;
|
|
34
|
+
removeComments?: boolean | 'safe' | 'all' | RegExp | ((comment: string) => boolean);
|
|
35
|
+
removeEmptyAttributes?: boolean;
|
|
36
|
+
removeRedundantAttributes?: boolean;
|
|
37
|
+
removeOptionalTags?: boolean;
|
|
38
|
+
removeUnusedCss?: boolean;
|
|
39
|
+
sortAttributes?: boolean | 'alphabetical' | 'frequency';
|
|
40
|
+
sortAttributesWithLists?: boolean | 'alphabetical' | 'frequency';
|
|
41
|
+
}
|
|
42
|
+
interface HtmlnanoPreset extends Omit<HtmlnanoOptions, 'skipConfigLoading'> {
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
declare const _default: HtmlnanoPreset;
|
|
46
|
+
|
|
47
|
+
export { _default as default };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import PostHTML from 'posthtml';
|
|
2
|
+
import { MinifyOptions } from 'terser';
|
|
3
|
+
import { Options } from 'cssnano';
|
|
4
|
+
import { Config } from 'svgo';
|
|
5
|
+
|
|
6
|
+
type PostHTMLTreeLike = [PostHTML.Node] & PostHTML.NodeAPI & {
|
|
7
|
+
options?: {
|
|
8
|
+
quoteAllAttributes?: boolean | undefined;
|
|
9
|
+
} | undefined;
|
|
10
|
+
render(): string;
|
|
11
|
+
render(node: PostHTML.Node | PostHTMLTreeLike, renderOptions?: any): string;
|
|
12
|
+
};
|
|
13
|
+
type MaybeArray<T> = T | Array<T>;
|
|
14
|
+
interface HtmlnanoOptions {
|
|
15
|
+
skipConfigLoading?: boolean;
|
|
16
|
+
skipInternalWarnings?: boolean;
|
|
17
|
+
collapseAttributeWhitespace?: boolean;
|
|
18
|
+
collapseBooleanAttributes?: {
|
|
19
|
+
amphtml?: boolean;
|
|
20
|
+
};
|
|
21
|
+
collapseWhitespace?: 'conservative' | 'all' | 'aggressive';
|
|
22
|
+
custom?: MaybeArray<(tree: PostHTMLTreeLike, options?: any) => (PostHTML.Node | PostHTMLTreeLike)>;
|
|
23
|
+
deduplicateAttributeValues?: boolean;
|
|
24
|
+
minifyUrls?: URL | string | false;
|
|
25
|
+
mergeStyles?: boolean;
|
|
26
|
+
mergeScripts?: boolean;
|
|
27
|
+
minifyCss?: Options | boolean;
|
|
28
|
+
minifyConditionalComments?: boolean;
|
|
29
|
+
minifyJs?: MinifyOptions | boolean;
|
|
30
|
+
minifyJson?: boolean;
|
|
31
|
+
minifySvg?: Config | boolean;
|
|
32
|
+
normalizeAttributeValues?: boolean;
|
|
33
|
+
removeAttributeQuotes?: boolean;
|
|
34
|
+
removeComments?: boolean | 'safe' | 'all' | RegExp | ((comment: string) => boolean);
|
|
35
|
+
removeEmptyAttributes?: boolean;
|
|
36
|
+
removeRedundantAttributes?: boolean;
|
|
37
|
+
removeOptionalTags?: boolean;
|
|
38
|
+
removeUnusedCss?: boolean;
|
|
39
|
+
sortAttributes?: boolean | 'alphabetical' | 'frequency';
|
|
40
|
+
sortAttributesWithLists?: boolean | 'alphabetical' | 'frequency';
|
|
41
|
+
}
|
|
42
|
+
interface HtmlnanoPreset extends Omit<HtmlnanoOptions, 'skipConfigLoading'> {
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
declare const _default: HtmlnanoPreset;
|
|
46
|
+
|
|
47
|
+
export { _default as default };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "htmlnano",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.5",
|
|
4
4
|
"description": "Modular HTML minifier, built on top of the PostHTML",
|
|
5
5
|
"author": "Kirill Maltsev <maltsevkirill@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"lint": "eslint --fix .",
|
|
12
12
|
"pretest": "npm run lint && npm run compile",
|
|
13
13
|
"test": ":",
|
|
14
|
-
"posttest": "mocha --timeout 5000 --require @swc-node/register --recursive --check-leaks --globals addresses",
|
|
14
|
+
"posttest": "mocha --timeout 5000 --require @swc-node/register --recursive --check-leaks --globals addresses 'test/**/*.ts'",
|
|
15
15
|
"prepare": "npm run compile"
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
@@ -21,22 +21,27 @@
|
|
|
21
21
|
"module": "./dist/index.mjs",
|
|
22
22
|
"exports": {
|
|
23
23
|
"./helpers": {
|
|
24
|
+
"types": "./dist/helpers.d.ts",
|
|
24
25
|
"import": "./dist/helpers.mjs",
|
|
25
26
|
"require": "./dist/helpers.js"
|
|
26
27
|
},
|
|
27
28
|
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
28
30
|
"import": "./dist/index.mjs",
|
|
29
31
|
"require": "./dist/index.js"
|
|
30
32
|
},
|
|
31
33
|
"./presets/ampSafe": {
|
|
34
|
+
"types": "./dist/presets/ampSafe.d.ts",
|
|
32
35
|
"import": "./dist/presets/ampSafe.mjs",
|
|
33
36
|
"require": "./dist/presets/ampSafe.js"
|
|
34
37
|
},
|
|
35
38
|
"./presets/max": {
|
|
39
|
+
"types": "./dist/presets/max.d.ts",
|
|
36
40
|
"import": "./dist/presets/max.mjs",
|
|
37
41
|
"require": "./dist/presets/max.js"
|
|
38
42
|
},
|
|
39
43
|
"./presets/safe": {
|
|
44
|
+
"types": "./dist/presets/safe.d.ts",
|
|
40
45
|
"import": "./dist/presets/safe.mjs",
|
|
41
46
|
"require": "./dist/presets/safe.js"
|
|
42
47
|
}
|
|
@@ -54,8 +59,10 @@
|
|
|
54
59
|
"posthtml": "^0.16.5"
|
|
55
60
|
},
|
|
56
61
|
"devDependencies": {
|
|
62
|
+
"@eslint/js": "^9.35.0",
|
|
57
63
|
"@stylistic/eslint-plugin": "^5.3.1",
|
|
58
64
|
"@swc-node/register": "^1.10.10",
|
|
65
|
+
"@types/mocha": "^10.0.10",
|
|
59
66
|
"@types/node": "^24.0.0",
|
|
60
67
|
"bunchee": "^6.5.1",
|
|
61
68
|
"cssnano": "^7.0.0",
|
|
@@ -75,7 +82,7 @@
|
|
|
75
82
|
"svgo": "^3.0.2",
|
|
76
83
|
"terser": "^5.21.0",
|
|
77
84
|
"typescript": "^5.8.3",
|
|
78
|
-
"typescript-eslint": "^8.
|
|
85
|
+
"typescript-eslint": "^8.44.0",
|
|
79
86
|
"uncss": "^0.17.3"
|
|
80
87
|
},
|
|
81
88
|
"peerDependencies": {
|
|
@@ -121,6 +128,5 @@
|
|
|
121
128
|
"bugs": {
|
|
122
129
|
"url": "https://github.com/posthtml/htmlnano/issues"
|
|
123
130
|
},
|
|
124
|
-
"homepage": "https://github.com/posthtml/htmlnano"
|
|
125
|
-
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
|
|
131
|
+
"homepage": "https://github.com/posthtml/htmlnano"
|
|
126
132
|
}
|