htmlnano 0.2.1 → 0.2.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/.eslintignore +1 -0
- package/CHANGELOG.md +45 -5
- package/README.md +101 -6
- package/lib/helpers.js +10 -0
- package/lib/htmlnano.js +1 -5
- package/lib/modules/collapseBooleanAttributes.js +4 -0
- package/lib/modules/mergeScripts.js +4 -1
- package/lib/modules/minifyCss.js +2 -6
- package/lib/modules/removeUnusedCss.js +147 -0
- package/lib/presets/ampSafe.js +1 -5
- package/lib/presets/max.js +7 -6
- package/lib/presets/safe.js +4 -1
- package/package.json +22 -14
- package/test.js +16 -0
package/.eslintignore
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -2,29 +2,56 @@
|
|
|
2
2
|
All notable changes to this project will be documented in this file.
|
|
3
3
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
|
4
4
|
|
|
5
|
+
## [0.2.5] - 2019-11-09
|
|
6
|
+
### Added
|
|
7
|
+
- Option to remove unused CSS using PurgeCSS [#84].
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- Keep the order of inline and external JS [#80].
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## [0.2.4] - 2019-07-11
|
|
14
|
+
### Fixed
|
|
15
|
+
- Remove crossorigin from boolean attribute [#78], [#79].
|
|
16
|
+
- Disable SVGO plugin convertShapeToPath in safe preset [#76].
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## [0.2.3] - 2019-02-14
|
|
20
|
+
### Fixed
|
|
21
|
+
- Keep `<g>` in SVG by default [#71].
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## [0.2.2] - 2019-01-03
|
|
25
|
+
### Added
|
|
26
|
+
- `removeUnusedCss` module [#36].
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- Bug when `tag === false` [#66].
|
|
30
|
+
- Add `crossorigin` to boolean attributes [#67].
|
|
31
|
+
|
|
5
32
|
|
|
6
33
|
## [0.2.1] - 2018-12-01
|
|
7
|
-
|
|
34
|
+
### Fixed
|
|
8
35
|
- Disable JS minifying on AMP pages [#65].
|
|
9
36
|
|
|
10
37
|
## [0.2.0] - 2018-09-14
|
|
11
|
-
|
|
38
|
+
### Breaking changes
|
|
12
39
|
- The API of `minifyCss` module has been changed since `cssnano` has been updated to version 4, which has a different API. Check the following resources for more info:
|
|
13
40
|
* [htmlnano docs](https://github.com/posthtml/htmlnano#minifycss)
|
|
14
41
|
* [cssnano docs](https://cssnano.co/guides/presets)
|
|
15
42
|
* Diff of commit [979f2c](https://github.com/posthtml/htmlnano/commit/979f2c821892c9e979e8b85f74ed0394330fceaf) with the changes of related docs.
|
|
16
43
|
|
|
17
|
-
|
|
44
|
+
### Added
|
|
18
45
|
- Add presets [#64].
|
|
19
46
|
- Add `collapseAttributeWhitespace` module for collapsing spaces in list-like attributes [#25].
|
|
20
47
|
- Add `deduplicateAttributeValues` module for de-duplicating values in list-like attributes [#39].
|
|
21
48
|
- Better support for AMP pages [#59].
|
|
22
49
|
- Collapse whitespaces between top-level tags [#24].
|
|
23
50
|
|
|
24
|
-
|
|
51
|
+
### Changed
|
|
25
52
|
- Improve whitespace normalization using `normalize-html-whitespace` [#21].
|
|
26
53
|
|
|
27
|
-
|
|
54
|
+
### Fixed
|
|
28
55
|
- Don't collapse `visible="false"` attributes in A-Frame pages [#62].
|
|
29
56
|
|
|
30
57
|
|
|
@@ -102,6 +129,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
102
129
|
|
|
103
130
|
|
|
104
131
|
|
|
132
|
+
[0.2.5]: https://github.com/posthtml/htmlnano/compare/0.2.4...0.2.5
|
|
133
|
+
[0.2.4]: https://github.com/posthtml/htmlnano/compare/0.2.3...0.2.4
|
|
134
|
+
[0.2.3]: https://github.com/posthtml/htmlnano/compare/0.2.2...0.2.3
|
|
135
|
+
[0.2.2]: https://github.com/posthtml/htmlnano/compare/0.2.1...0.2.2
|
|
105
136
|
[0.2.1]: https://github.com/posthtml/htmlnano/compare/0.2.0...0.2.1
|
|
106
137
|
[0.2.0]: https://github.com/posthtml/htmlnano/compare/0.1.10...0.2.0
|
|
107
138
|
[0.1.10]: https://github.com/posthtml/htmlnano/compare/0.1.9...0.1.10
|
|
@@ -116,6 +147,14 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
116
147
|
[0.1.1]: https://github.com/posthtml/htmlnano/compare/0.1.0...0.1.1
|
|
117
148
|
|
|
118
149
|
|
|
150
|
+
[#84]: https://github.com/posthtml/htmlnano/issues/84
|
|
151
|
+
[#80]: https://github.com/posthtml/htmlnano/issues/80
|
|
152
|
+
[#79]: https://github.com/posthtml/htmlnano/issues/79
|
|
153
|
+
[#78]: https://github.com/posthtml/htmlnano/issues/78
|
|
154
|
+
[#76]: https://github.com/posthtml/htmlnano/issues/76
|
|
155
|
+
[#71]: https://github.com/posthtml/htmlnano/issues/71
|
|
156
|
+
[#67]: https://github.com/posthtml/htmlnano/issues/67
|
|
157
|
+
[#66]: https://github.com/posthtml/htmlnano/issues/66
|
|
119
158
|
[#65]: https://github.com/posthtml/htmlnano/issues/65
|
|
120
159
|
[#64]: https://github.com/posthtml/htmlnano/issues/64
|
|
121
160
|
[#62]: https://github.com/posthtml/htmlnano/issues/62
|
|
@@ -126,6 +165,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
126
165
|
[#47]: https://github.com/posthtml/htmlnano/issues/47
|
|
127
166
|
[#42]: https://github.com/posthtml/htmlnano/issues/42
|
|
128
167
|
[#39]: https://github.com/posthtml/htmlnano/issues/39
|
|
168
|
+
[#36]: https://github.com/posthtml/htmlnano/issues/36
|
|
129
169
|
[#31]: https://github.com/posthtml/htmlnano/issues/31
|
|
130
170
|
[#30]: https://github.com/posthtml/htmlnano/issues/30
|
|
131
171
|
[#28]: https://github.com/posthtml/htmlnano/issues/28
|
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Modular HTML minifier, built on top of the [PostHTML](https://github.com/posthtml/posthtml). Inspired by [cssnano](http://cssnano.co/).
|
|
6
6
|
|
|
7
|
+
> The author of htmlnano is available for hire as a full stack web developer: https://kirillmaltsev.net/services
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
## [Benchmark](https://github.com/maltsev/html-minifiers-benchmark/blob/master/README.md)
|
|
@@ -75,8 +76,7 @@ const posthtmlPlugins = [
|
|
|
75
76
|
require('htmlnano')(options)
|
|
76
77
|
];
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
posthtml(posthtmlPlugins, preset)
|
|
79
|
+
posthtml(posthtmlPlugins)
|
|
80
80
|
.process(html)
|
|
81
81
|
.then(function (result) {
|
|
82
82
|
// result.html is minified
|
|
@@ -271,6 +271,81 @@ Minified:
|
|
|
271
271
|
<img src="foo.jpg" alt="">
|
|
272
272
|
```
|
|
273
273
|
|
|
274
|
+
### removeUnusedCss
|
|
275
|
+
|
|
276
|
+
Removes unused CSS inside `<style>` tags with either [uncss](https://github.com/uncss/uncss)
|
|
277
|
+
or [PurgeCSS](https://github.com/FullHuman/purgecss).
|
|
278
|
+
|
|
279
|
+
#### With uncss
|
|
280
|
+
|
|
281
|
+
##### Options
|
|
282
|
+
See [the documentation of uncss](https://github.com/uncss/uncss) for all supported options.
|
|
283
|
+
|
|
284
|
+
uncss options can be passed directly to the `removeUnusedCss` module:
|
|
285
|
+
```js
|
|
286
|
+
htmlnano.process(html, {
|
|
287
|
+
removeUnusedCss: {
|
|
288
|
+
ignore: ['.do-not-remove']
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
The following uncss options are ignored if passed to the module:
|
|
294
|
+
|
|
295
|
+
- `stylesheets`
|
|
296
|
+
- `ignoreSheets`
|
|
297
|
+
- `raw`
|
|
298
|
+
|
|
299
|
+
#### With PurgeCSS
|
|
300
|
+
|
|
301
|
+
Use PurgeCSS instead of uncss by adding `tool: 'purgeCSS'` to the options.
|
|
302
|
+
|
|
303
|
+
##### Options
|
|
304
|
+
|
|
305
|
+
See [the documentation of PurgeCSS](https://www.purgecss.com) for all supported options.
|
|
306
|
+
|
|
307
|
+
PurgeCSS options can be passed directly to the `removeUnusedCss` module:
|
|
308
|
+
```js
|
|
309
|
+
htmlnano.process(html, {
|
|
310
|
+
removeUnusedCss: {
|
|
311
|
+
tool: 'purgeCSS',
|
|
312
|
+
whitelist: ['.do-not-remove']
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
The following PurgeCSS options are ignored if passed to the module:
|
|
318
|
+
|
|
319
|
+
- `content`
|
|
320
|
+
- `css`
|
|
321
|
+
- `extractors`
|
|
322
|
+
|
|
323
|
+
##### Example
|
|
324
|
+
Source:
|
|
325
|
+
```html
|
|
326
|
+
<div class="b">
|
|
327
|
+
<style>
|
|
328
|
+
.a {
|
|
329
|
+
margin: 10px 10px 10px 10px;
|
|
330
|
+
}
|
|
331
|
+
.b {
|
|
332
|
+
color: #ff0000;
|
|
333
|
+
}
|
|
334
|
+
</style>
|
|
335
|
+
</div>
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Optimized:
|
|
339
|
+
```html
|
|
340
|
+
<div class="b">
|
|
341
|
+
<style>
|
|
342
|
+
.b {
|
|
343
|
+
color: #ff0000;
|
|
344
|
+
}
|
|
345
|
+
</style>
|
|
346
|
+
</div>
|
|
347
|
+
```
|
|
348
|
+
|
|
274
349
|
|
|
275
350
|
### minifyCss
|
|
276
351
|
Minifies CSS with [cssnano](http://cssnano.co/) inside `<style>` tags and `style` attributes.
|
|
@@ -314,10 +389,20 @@ Minified:
|
|
|
314
389
|
|
|
315
390
|
|
|
316
391
|
### minifyJs
|
|
317
|
-
Minifies JS
|
|
392
|
+
Minifies JS using [Terser](https://github.com/fabiosantoscode/terser) inside `<script>` tags.
|
|
318
393
|
|
|
319
394
|
##### Options
|
|
320
|
-
See [the
|
|
395
|
+
See [the documentation of Terser](https://github.com/fabiosantoscode/terser#api-reference) for all supported options.
|
|
396
|
+
Terser options can be passed directly to the `minifyJs` module:
|
|
397
|
+
```js
|
|
398
|
+
htmlnano.process(html, {
|
|
399
|
+
minifyJs: {
|
|
400
|
+
output: { quote_style: 1 },
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
|
|
321
406
|
|
|
322
407
|
##### Example
|
|
323
408
|
Source:
|
|
@@ -360,10 +445,20 @@ Minified:
|
|
|
360
445
|
|
|
361
446
|
|
|
362
447
|
### minifySvg
|
|
363
|
-
Minifies SVG inside `<svg>` tags
|
|
448
|
+
Minifies SVG inside `<svg>` tags using [SVGO](https://github.com/svg/svgo/).
|
|
364
449
|
|
|
365
450
|
##### Options
|
|
366
|
-
See [the documentation of SVGO](https://github.com/svg/svgo/blob/master/README.md)
|
|
451
|
+
See [the documentation of SVGO](https://github.com/svg/svgo/blob/master/README.md) for all supported options.
|
|
452
|
+
SVGO options can be passed directly to the `minifySvg` module:
|
|
453
|
+
```js
|
|
454
|
+
htmlnano.process(html, {
|
|
455
|
+
minifySvg: {
|
|
456
|
+
plugins: [
|
|
457
|
+
{ collapseGroups: false },
|
|
458
|
+
],
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
```
|
|
367
462
|
|
|
368
463
|
##### Example
|
|
369
464
|
Source:
|
package/lib/helpers.js
CHANGED
|
@@ -6,6 +6,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.isAmpBoilerplate = isAmpBoilerplate;
|
|
7
7
|
exports.isComment = isComment;
|
|
8
8
|
exports.isConditionalComment = isConditionalComment;
|
|
9
|
+
exports.isStyleNode = isStyleNode;
|
|
10
|
+
exports.extractCssFromStyleNode = extractCssFromStyleNode;
|
|
9
11
|
var ampBoilerplateAttributes = ['amp-boilerplate', 'amp4ads-boilerplate', 'amp4email-boilerplate'];
|
|
10
12
|
|
|
11
13
|
function isAmpBoilerplate(node) {
|
|
@@ -48,4 +50,12 @@ function isComment(content) {
|
|
|
48
50
|
|
|
49
51
|
function isConditionalComment(content) {
|
|
50
52
|
return (content || '').trim().search(/<!--\[if/) === 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isStyleNode(node) {
|
|
56
|
+
return node.tag === 'style' && !isAmpBoilerplate(node) && 'content' in node && node.content.length > 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function extractCssFromStyleNode(node) {
|
|
60
|
+
return Array.isArray(node.content) ? node.content.join(' ') : node.content;
|
|
51
61
|
}
|
package/lib/htmlnano.js
CHANGED
|
@@ -4,10 +4,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
|
|
7
|
-
var _objectAssign = require('object-assign');
|
|
8
|
-
|
|
9
|
-
var _objectAssign2 = _interopRequireDefault(_objectAssign);
|
|
10
|
-
|
|
11
7
|
var _posthtml = require('posthtml');
|
|
12
8
|
|
|
13
9
|
var _posthtml2 = _interopRequireDefault(_posthtml);
|
|
@@ -31,7 +27,7 @@ function htmlnano() {
|
|
|
31
27
|
var preset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _safe2.default;
|
|
32
28
|
|
|
33
29
|
return function minifier(tree) {
|
|
34
|
-
options =
|
|
30
|
+
options = Object.assign({}, preset, options);
|
|
35
31
|
var promise = Promise.resolve(tree);
|
|
36
32
|
|
|
37
33
|
var _loop = function _loop(moduleName) {
|
|
@@ -19,6 +19,10 @@ function collapseBooleanAttributes(tree, options, moduleOptions) {
|
|
|
19
19
|
for (var _iterator = Object.keys(node.attrs)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
|
|
20
20
|
var attrName = _step.value;
|
|
21
21
|
|
|
22
|
+
if (!node.tag) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
|
|
22
26
|
if (node.tag.search('a-') === 0 && attrName === 'visible') {
|
|
23
27
|
continue;
|
|
24
28
|
}
|
|
@@ -7,10 +7,12 @@ exports.default = mergeScripts;
|
|
|
7
7
|
/* Merge multiple <script> into one */
|
|
8
8
|
function mergeScripts(tree) {
|
|
9
9
|
var scriptNodesIndex = {};
|
|
10
|
+
var scriptSrcIndex = 1;
|
|
10
11
|
|
|
11
12
|
tree.match({ tag: 'script' }, function (node) {
|
|
12
13
|
var nodeAttrs = node.attrs || {};
|
|
13
14
|
if (nodeAttrs.src) {
|
|
15
|
+
scriptSrcIndex++;
|
|
14
16
|
return node;
|
|
15
17
|
}
|
|
16
18
|
|
|
@@ -24,7 +26,8 @@ function mergeScripts(tree) {
|
|
|
24
26
|
class: nodeAttrs.class,
|
|
25
27
|
type: scriptType,
|
|
26
28
|
defer: nodeAttrs.defer !== undefined,
|
|
27
|
-
async: nodeAttrs.async !== undefined
|
|
29
|
+
async: nodeAttrs.async !== undefined,
|
|
30
|
+
index: scriptSrcIndex
|
|
28
31
|
});
|
|
29
32
|
if (!scriptNodesIndex[scriptKey]) {
|
|
30
33
|
scriptNodesIndex[scriptKey] = [];
|
package/lib/modules/minifyCss.js
CHANGED
|
@@ -24,7 +24,7 @@ var postcssOptions = {
|
|
|
24
24
|
function minifyCss(tree, options, cssnanoOptions) {
|
|
25
25
|
var promises = [];
|
|
26
26
|
tree.walk(function (node) {
|
|
27
|
-
if (isStyleNode(node)) {
|
|
27
|
+
if ((0, _helpers.isStyleNode)(node)) {
|
|
28
28
|
promises.push(processStyleNode(node, cssnanoOptions));
|
|
29
29
|
} else if (node.attrs && node.attrs.style) {
|
|
30
30
|
promises.push(processStyleAttr(node, cssnanoOptions));
|
|
@@ -38,12 +38,8 @@ function minifyCss(tree, options, cssnanoOptions) {
|
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function isStyleNode(node) {
|
|
42
|
-
return node.tag === 'style' && !(0, _helpers.isAmpBoilerplate)(node) && node.content && node.content.length;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
41
|
function processStyleNode(styleNode, cssnanoOptions) {
|
|
46
|
-
var css =
|
|
42
|
+
var css = (0, _helpers.extractCssFromStyleNode)(styleNode);
|
|
47
43
|
return _cssnano2.default.process(css, postcssOptions, cssnanoOptions).then(function (result) {
|
|
48
44
|
return styleNode.content = [result.css];
|
|
49
45
|
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
|
|
8
|
+
|
|
9
|
+
exports.default = removeUnusedCss;
|
|
10
|
+
|
|
11
|
+
var _helpers = require('../helpers');
|
|
12
|
+
|
|
13
|
+
var _uncss = require('uncss');
|
|
14
|
+
|
|
15
|
+
var _uncss2 = _interopRequireDefault(_uncss);
|
|
16
|
+
|
|
17
|
+
var _purgecss = require('purgecss');
|
|
18
|
+
|
|
19
|
+
var _purgecss2 = _interopRequireDefault(_purgecss);
|
|
20
|
+
|
|
21
|
+
var _posthtmlRender = require('posthtml-render');
|
|
22
|
+
|
|
23
|
+
var _posthtmlRender2 = _interopRequireDefault(_posthtmlRender);
|
|
24
|
+
|
|
25
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
26
|
+
|
|
27
|
+
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
|
|
28
|
+
|
|
29
|
+
// These options must be set and shouldn't be overriden to ensure uncss doesn't look at linked stylesheets.
|
|
30
|
+
var uncssOptions = {
|
|
31
|
+
ignoreSheets: [/\s*/],
|
|
32
|
+
stylesheets: []
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function processStyleNodeUnCSS(html, styleNode, uncssOptions) {
|
|
36
|
+
var css = (0, _helpers.extractCssFromStyleNode)(styleNode);
|
|
37
|
+
|
|
38
|
+
return runUncss(html, css, uncssOptions).then(function (css) {
|
|
39
|
+
// uncss may have left some style tags empty
|
|
40
|
+
if (css.trim().length === 0) {
|
|
41
|
+
styleNode.tag = false;
|
|
42
|
+
styleNode.content = [];
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
styleNode.content = [css];
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function runUncss(html, css, userOptions) {
|
|
50
|
+
if ((typeof userOptions === 'undefined' ? 'undefined' : _typeof(userOptions)) !== 'object') {
|
|
51
|
+
userOptions = {};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
var options = Object.assign({}, userOptions, uncssOptions);
|
|
55
|
+
return new Promise(function (resolve, reject) {
|
|
56
|
+
options.raw = css;
|
|
57
|
+
(0, _uncss2.default)(html, options, function (error, output) {
|
|
58
|
+
if (error) {
|
|
59
|
+
reject(error);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
resolve(output);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
var purgeFromHtml = function purgeFromHtml(tree) {
|
|
68
|
+
// content is not used as we can directly used the parsed HTML,
|
|
69
|
+
// making the process faster
|
|
70
|
+
var selectors = [];
|
|
71
|
+
|
|
72
|
+
tree.walk(function (node) {
|
|
73
|
+
var classes = node.attrs && node.attrs.class && node.attrs.class.split(' ') || [];
|
|
74
|
+
var ids = node.attrs && node.attrs.id && node.attrs.id.split(' ') || [];
|
|
75
|
+
selectors.push.apply(selectors, _toConsumableArray(classes).concat(_toConsumableArray(ids)));
|
|
76
|
+
node.tag && selectors.push(node.tag);
|
|
77
|
+
return node;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return function () {
|
|
81
|
+
return selectors;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
function processStyleNodePurgeCSS(tree, styleNode, purgecssOptions) {
|
|
86
|
+
var css = (0, _helpers.extractCssFromStyleNode)(styleNode);
|
|
87
|
+
return runPurgecss(tree, css, purgecssOptions).then(function (css) {
|
|
88
|
+
if (css.trim().length === 0) {
|
|
89
|
+
styleNode.tag = false;
|
|
90
|
+
styleNode.content = [];
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
styleNode.content = [css];
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function runPurgecss(tree, css, userOptions) {
|
|
98
|
+
if ((typeof userOptions === 'undefined' ? 'undefined' : _typeof(userOptions)) !== 'object') {
|
|
99
|
+
userOptions = {};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
var options = Object.assign({}, userOptions, {
|
|
103
|
+
content: [{
|
|
104
|
+
raw: tree,
|
|
105
|
+
extension: 'html'
|
|
106
|
+
}],
|
|
107
|
+
css: [{
|
|
108
|
+
raw: css,
|
|
109
|
+
extension: 'css'
|
|
110
|
+
}],
|
|
111
|
+
extractors: [{
|
|
112
|
+
extractor: purgeFromHtml(tree),
|
|
113
|
+
extensions: ['html']
|
|
114
|
+
}]
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return new Promise(function (resolve, reject) {
|
|
118
|
+
try {
|
|
119
|
+
var purgeCss = new _purgecss2.default(options);
|
|
120
|
+
var purgecssResult = purgeCss.purge()[0];
|
|
121
|
+
resolve(purgecssResult.css);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
reject(err);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Remove unused CSS */
|
|
129
|
+
function removeUnusedCss(tree, options, userOptions) {
|
|
130
|
+
var promises = [];
|
|
131
|
+
var html = userOptions.tool !== 'purgeCSS' && (0, _posthtmlRender2.default)(tree);
|
|
132
|
+
|
|
133
|
+
tree.walk(function (node) {
|
|
134
|
+
if ((0, _helpers.isStyleNode)(node)) {
|
|
135
|
+
if (userOptions.tool === 'purgeCSS') {
|
|
136
|
+
promises.push(processStyleNodePurgeCSS(tree, node, userOptions));
|
|
137
|
+
} else {
|
|
138
|
+
promises.push(processStyleNodeUnCSS(html, node, userOptions));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return node;
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return Promise.all(promises).then(function () {
|
|
145
|
+
return tree;
|
|
146
|
+
});
|
|
147
|
+
}
|
package/lib/presets/ampSafe.js
CHANGED
|
@@ -4,10 +4,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
|
|
7
|
-
var _objectAssign = require('object-assign');
|
|
8
|
-
|
|
9
|
-
var _objectAssign2 = _interopRequireDefault(_objectAssign);
|
|
10
|
-
|
|
11
7
|
var _safe = require('./safe');
|
|
12
8
|
|
|
13
9
|
var _safe2 = _interopRequireDefault(_safe);
|
|
@@ -19,7 +15,7 @@ function _interopRequireDefault(obj) {
|
|
|
19
15
|
/**
|
|
20
16
|
* A safe preset for AMP pages (https://www.ampproject.org)
|
|
21
17
|
*/
|
|
22
|
-
exports.default =
|
|
18
|
+
exports.default = Object.assign({}, _safe2.default, {
|
|
23
19
|
collapseBooleanAttributes: {
|
|
24
20
|
amphtml: true
|
|
25
21
|
},
|
package/lib/presets/max.js
CHANGED
|
@@ -4,10 +4,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
|
|
7
|
-
var _objectAssign = require('object-assign');
|
|
8
|
-
|
|
9
|
-
var _objectAssign2 = _interopRequireDefault(_objectAssign);
|
|
10
|
-
|
|
11
7
|
var _safe = require('./safe');
|
|
12
8
|
|
|
13
9
|
var _safe2 = _interopRequireDefault(_safe);
|
|
@@ -19,8 +15,13 @@ function _interopRequireDefault(obj) {
|
|
|
19
15
|
/**
|
|
20
16
|
* Maximal minification (might break some pages)
|
|
21
17
|
*/
|
|
22
|
-
exports.default =
|
|
18
|
+
exports.default = Object.assign({}, _safe2.default, {
|
|
23
19
|
collapseWhitespace: 'all',
|
|
24
20
|
removeComments: 'all',
|
|
25
|
-
removeRedundantAttributes: true
|
|
21
|
+
removeRedundantAttributes: true,
|
|
22
|
+
removeUnusedCss: {},
|
|
23
|
+
minifyCss: {
|
|
24
|
+
preset: 'default'
|
|
25
|
+
},
|
|
26
|
+
minifySvg: {}
|
|
26
27
|
});
|
package/lib/presets/safe.js
CHANGED
|
@@ -16,12 +16,15 @@ exports.default = {
|
|
|
16
16
|
deduplicateAttributeValues: true,
|
|
17
17
|
mergeScripts: true,
|
|
18
18
|
mergeStyles: true,
|
|
19
|
+
removeUnusedCss: false,
|
|
19
20
|
minifyCss: {
|
|
20
21
|
preset: 'default'
|
|
21
22
|
},
|
|
22
23
|
minifyJs: {},
|
|
23
24
|
minifyJson: {},
|
|
24
|
-
minifySvg: {
|
|
25
|
+
minifySvg: {
|
|
26
|
+
plugins: [{ collapseGroups: false }, { convertShapeToPath: false }]
|
|
27
|
+
},
|
|
25
28
|
removeEmptyAttributes: true,
|
|
26
29
|
removeRedundantAttributes: false,
|
|
27
30
|
removeComments: 'safe'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "htmlnano",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Modular HTML minifier, built on top of the PostHTML",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "Kirill Maltsev <maltsevkirill@gmail.com>",
|
|
@@ -11,7 +11,13 @@
|
|
|
11
11
|
"pretest": "npm run lint && npm run compile",
|
|
12
12
|
"test": ":",
|
|
13
13
|
"posttest": "mocha --require babel-core/register --recursive --check-leaks --globals addresses",
|
|
14
|
-
"prepare": "npm run compile"
|
|
14
|
+
"prepare": "npm run compile",
|
|
15
|
+
"release:patch": "release-it patch -n"
|
|
16
|
+
},
|
|
17
|
+
"release-it": {
|
|
18
|
+
"hooks": {
|
|
19
|
+
"before:init": "npm test"
|
|
20
|
+
}
|
|
15
21
|
},
|
|
16
22
|
"keywords": [
|
|
17
23
|
"posthtml",
|
|
@@ -26,23 +32,25 @@
|
|
|
26
32
|
]
|
|
27
33
|
},
|
|
28
34
|
"dependencies": {
|
|
29
|
-
"cssnano": "^4.1.
|
|
30
|
-
"normalize-html-whitespace": "^0.
|
|
31
|
-
"
|
|
32
|
-
"posthtml": "^
|
|
33
|
-
"
|
|
34
|
-
"svgo": "^1.
|
|
35
|
-
"terser": "^3.
|
|
35
|
+
"cssnano": "^4.1.10",
|
|
36
|
+
"normalize-html-whitespace": "^1.0.0",
|
|
37
|
+
"posthtml": "^0.12.0",
|
|
38
|
+
"posthtml-render": "^1.1.5",
|
|
39
|
+
"purgecss": "^1.4.0",
|
|
40
|
+
"svgo": "^1.3.2",
|
|
41
|
+
"terser": "^4.3.9",
|
|
42
|
+
"uncss": "^0.17.2"
|
|
36
43
|
},
|
|
37
44
|
"devDependencies": {
|
|
38
45
|
"babel-cli": "^6.26.0",
|
|
39
46
|
"babel-core": "^6.26.3",
|
|
40
|
-
"babel-eslint": "^10.0.
|
|
47
|
+
"babel-eslint": "^10.0.3",
|
|
41
48
|
"babel-preset-env": "^1.7.0",
|
|
42
|
-
"eslint": "^
|
|
43
|
-
"expect": "^
|
|
44
|
-
"mocha": "^
|
|
45
|
-
"
|
|
49
|
+
"eslint": "^6.6.0",
|
|
50
|
+
"expect": "^24.9.0",
|
|
51
|
+
"mocha": "^6.2.2",
|
|
52
|
+
"release-it": "^12.4.3",
|
|
53
|
+
"rimraf": "^3.0.0"
|
|
46
54
|
},
|
|
47
55
|
"repository": {
|
|
48
56
|
"type": "git",
|
package/test.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const htmlnano = require('./index');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const preset = require('./lib/presets/max').default;
|
|
4
|
+
|
|
5
|
+
const options = {};
|
|
6
|
+
|
|
7
|
+
const html = fs.readFileSync('./test.html', 'utf8');
|
|
8
|
+
|
|
9
|
+
htmlnano
|
|
10
|
+
.process(html, options, preset)
|
|
11
|
+
.then(function (result) {
|
|
12
|
+
console.log(result.html);
|
|
13
|
+
})
|
|
14
|
+
.catch(function (err) {
|
|
15
|
+
console.error(err);
|
|
16
|
+
});
|