htmlnano 0.2.7 → 0.2.8
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/CHANGELOG.md +25 -0
- package/README.md +128 -0
- package/lib/helpers.js +4 -22
- package/lib/htmlnano.js +17 -47
- package/lib/modules/collapseAttributeWhitespace.js +6 -23
- package/lib/modules/collapseBooleanAttributes.js +5 -7
- package/lib/modules/collapseWhitespace.js +19 -24
- package/lib/modules/custom.js +2 -2
- package/lib/modules/deduplicateAttributeValues.js +8 -8
- package/lib/modules/mergeScripts.js +12 -17
- package/lib/modules/mergeStyles.js +8 -8
- package/lib/modules/minifyConditionalComments.js +52 -0
- package/lib/modules/minifyCss.js +13 -17
- package/lib/modules/minifyJs.js +19 -21
- package/lib/modules/minifyJson.js +3 -3
- package/lib/modules/minifySvg.js +9 -12
- package/lib/modules/minifyUrls.js +58 -55
- package/lib/modules/removeAttributeQuotes.js +1 -1
- package/lib/modules/removeComments.js +7 -9
- package/lib/modules/removeEmptyAttributes.js +5 -22
- package/lib/modules/removeOptionalTags.js +220 -0
- package/lib/modules/removeRedundantAttributes.js +33 -32
- package/lib/modules/removeUnusedCss.js +28 -52
- package/lib/modules/sortAttributes.js +120 -0
- package/lib/modules/sortAttributesWithLists.js +120 -7
- package/lib/presets/ampSafe.js +5 -55
- package/lib/presets/max.js +8 -56
- package/lib/presets/safe.js +7 -4
- package/package.json +16 -7
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@ 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
5
|
|
|
6
|
+
## [0.2.8] - 2020-11-15
|
|
7
|
+
### Added
|
|
8
|
+
- [`removeOptionalTags`](https://github.com/posthtml/htmlnano#removeoptionaltags) [#110].
|
|
9
|
+
- [`sortAttributes`](https://github.com/posthtml/htmlnano#removeoptionaltags) [#113].
|
|
10
|
+
- `source[src]` and `srcset` support to `minifyUrls` [#117].
|
|
11
|
+
- [`minifyConditionalComments`](https://github.com/posthtml/htmlnano#minifyconditionalcomments) [#119].
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- Sort by frequency `sortAttributesWithLists` [#111].
|
|
15
|
+
- Strip more spaces in `collapseWhitespace` [#112].
|
|
16
|
+
- Remove `loading="eager"` from `<img>` and `<iframe>` [#114].
|
|
17
|
+
- Remove redundant `type` from `<script>` [#114].
|
|
18
|
+
- Strip whitespaces between textnode and element [#116].
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
6
22
|
## [0.2.7] - 2020-10-17
|
|
7
23
|
### Added
|
|
8
24
|
- More aggressive whitespace removal option [#90].
|
|
@@ -152,6 +168,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
152
168
|
- Remove attributes that contains only white spaces.
|
|
153
169
|
|
|
154
170
|
|
|
171
|
+
[0.2.8]: https://github.com/posthtml/htmlnano/compare/0.2.7...0.2.8
|
|
155
172
|
[0.2.7]: https://github.com/posthtml/htmlnano/compare/0.2.6...0.2.7
|
|
156
173
|
[0.2.6]: https://github.com/posthtml/htmlnano/compare/0.2.5...0.2.6
|
|
157
174
|
[0.2.5]: https://github.com/posthtml/htmlnano/compare/0.2.4...0.2.5
|
|
@@ -172,6 +189,14 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
172
189
|
[0.1.1]: https://github.com/posthtml/htmlnano/compare/0.1.0...0.1.1
|
|
173
190
|
|
|
174
191
|
|
|
192
|
+
[#119]: https://github.com/posthtml/htmlnano/issues/119
|
|
193
|
+
[#117]: https://github.com/posthtml/htmlnano/issues/117
|
|
194
|
+
[#116]: https://github.com/posthtml/htmlnano/issues/116
|
|
195
|
+
[#114]: https://github.com/posthtml/htmlnano/issues/114
|
|
196
|
+
[#113]: https://github.com/posthtml/htmlnano/issues/113
|
|
197
|
+
[#112]: https://github.com/posthtml/htmlnano/issues/112
|
|
198
|
+
[#111]: https://github.com/posthtml/htmlnano/issues/111
|
|
199
|
+
[#110]: https://github.com/posthtml/htmlnano/issues/110
|
|
175
200
|
[#107]: https://github.com/posthtml/htmlnano/issues/107
|
|
176
201
|
[#108]: https://github.com/posthtml/htmlnano/issues/108
|
|
177
202
|
[#102]: https://github.com/posthtml/htmlnano/issues/102
|
package/README.md
CHANGED
|
@@ -529,6 +529,49 @@ Minified:
|
|
|
529
529
|
<svg baseProfile="full" width="300" height="200" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="red"/><circle cx="150" cy="100" r="80" fill="green"/><text x="150" y="125" font-size="60" text-anchor="middle" fill="#fff">SVG</text></svg>
|
|
530
530
|
```
|
|
531
531
|
|
|
532
|
+
### minifyConditionalComments
|
|
533
|
+
|
|
534
|
+
Minify content inside conditional comments.
|
|
535
|
+
|
|
536
|
+
##### Example
|
|
537
|
+
|
|
538
|
+
Source:
|
|
539
|
+
|
|
540
|
+
```html
|
|
541
|
+
<!--[if lte IE 7]>
|
|
542
|
+
<style type="text/css">
|
|
543
|
+
.title {
|
|
544
|
+
color: red;
|
|
545
|
+
}
|
|
546
|
+
</style>
|
|
547
|
+
<![endif]-->
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
Minified:
|
|
551
|
+
|
|
552
|
+
```html
|
|
553
|
+
<!--[if lte IE 7]><style>.title {color:red}</style><![endif]-->
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
##### Notice
|
|
557
|
+
|
|
558
|
+
Due to [the limitation of PostHTML](https://github.com/posthtml/posthtml-parser/issues/9) (which is actually a issue from upstream [htmlparser2](https://github.com/fb55/htmlparser2/pull/146)), following html snippet is not supported:
|
|
559
|
+
|
|
560
|
+
```html
|
|
561
|
+
<!--[if lt IE 7]><html class="no-js ie6"><![endif]-->
|
|
562
|
+
<!--[if IE 7]><html class="no-js ie7"><![endif]-->
|
|
563
|
+
<!--[if IE 8]><html class="no-js ie8"><![endif]-->
|
|
564
|
+
<!--[if gt IE 8]><!--><html class="no-js"><!--<![endif]-->
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
Which will result in:
|
|
568
|
+
|
|
569
|
+
```html
|
|
570
|
+
<!--[if lt IE 7]><html class="no-js ie6"></html><![endif]-->
|
|
571
|
+
<!--[if IE 7]><html class="no-js ie7"></html><![endif]-->
|
|
572
|
+
<!--[if IE 8]><html class="no-js ie8"></html><![endif]-->
|
|
573
|
+
<!--[if gt IE 8]><!--><html class="no-js"></html><!--<![endif]-->
|
|
574
|
+
```
|
|
532
575
|
|
|
533
576
|
### removeRedundantAttributes
|
|
534
577
|
Removes redundant attributes from tags if they contain default values:
|
|
@@ -685,7 +728,15 @@ Sort values in list-like attributes (`class`, `rel`, `ping`).
|
|
|
685
728
|
|
|
686
729
|
The module won't impact the plain-text size of the output. However it will improve the compression ratio of gzip/brotli used in HTTP compression.
|
|
687
730
|
|
|
731
|
+
##### Options
|
|
732
|
+
|
|
733
|
+
- `alphabetical`: Default option. Sort attribute values in alphabetical order.
|
|
734
|
+
- `frequency`: Sort attribute values by frequency.
|
|
735
|
+
|
|
688
736
|
##### Example
|
|
737
|
+
|
|
738
|
+
**alphabetical**
|
|
739
|
+
|
|
689
740
|
Source:
|
|
690
741
|
```html
|
|
691
742
|
<div class="foo baz bar">click</div>
|
|
@@ -696,6 +747,58 @@ Processed:
|
|
|
696
747
|
<div class="bar baz foo">click</div>
|
|
697
748
|
```
|
|
698
749
|
|
|
750
|
+
**frequency**
|
|
751
|
+
|
|
752
|
+
Source:
|
|
753
|
+
```html
|
|
754
|
+
<div class="foo baz bar"></div><div class="bar foo"></div>
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
Processed:
|
|
758
|
+
```html
|
|
759
|
+
<div class="foo bar baz"></div><div class="foo bar"></div>
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
### sortAttributes
|
|
763
|
+
Sort attributes inside elements.
|
|
764
|
+
|
|
765
|
+
The module won't impact the plain-text size of the output. However it will improve the compression ratio of gzip/brotli used in HTTP compression.
|
|
766
|
+
|
|
767
|
+
##### Options
|
|
768
|
+
|
|
769
|
+
- `alphabetical`: Default option. Sort attributes in alphabetical order.
|
|
770
|
+
- `frequency`: Sort attributes by frequency.
|
|
771
|
+
|
|
772
|
+
##### Example
|
|
773
|
+
|
|
774
|
+
**alphabetical**
|
|
775
|
+
|
|
776
|
+
Source:
|
|
777
|
+
```html
|
|
778
|
+
<input type="text" class="form-control" name="testInput" autofocus="" autocomplete="off" id="testId">
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
Processed:
|
|
782
|
+
```html
|
|
783
|
+
<input autocomplete="off" autofocus="" class="form-control" id="testId" name="testInput" type="text">
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
**frequency**
|
|
787
|
+
|
|
788
|
+
Source:
|
|
789
|
+
```html
|
|
790
|
+
<input type="text" class="form-control" name="testInput" id="testId">
|
|
791
|
+
<a id="testId" href="#" class="testClass"></a>
|
|
792
|
+
<img width="20" src="../images/image.png" height="40" alt="image" class="cls" id="id2">
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
Processed:
|
|
796
|
+
```html
|
|
797
|
+
<input class="form-control" id="testId" type="text" name="testInput">
|
|
798
|
+
<a class="testClass" id="testId" href="#"></a>
|
|
799
|
+
<img class="cls" id="id2" width="20" src="../images/image.png" height="40" alt="image">
|
|
800
|
+
```
|
|
801
|
+
|
|
699
802
|
### minifyUrls
|
|
700
803
|
Convert absolute URL to relative URL using [relateurl](https://www.npmjs.com/package/relateurl).
|
|
701
804
|
|
|
@@ -773,6 +876,31 @@ Minified:
|
|
|
773
876
|
<a href="../bar">bar</a>
|
|
774
877
|
```
|
|
775
878
|
|
|
879
|
+
## removeOptionalTags
|
|
880
|
+
Remove certain tags that can be omitted, see [HTML Standard - 13.1.2.4 Optional tags](https://html.spec.whatwg.org/multipage/syntax.html#optional-tags).
|
|
881
|
+
|
|
882
|
+
##### Example
|
|
883
|
+
|
|
884
|
+
Source:
|
|
885
|
+
|
|
886
|
+
```html
|
|
887
|
+
<html><head><title>Title</title></head><body><p>Hi</p></body></html>
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
Minified:
|
|
891
|
+
|
|
892
|
+
```html
|
|
893
|
+
<title>Title</title><p>Hi</p>
|
|
894
|
+
```
|
|
895
|
+
##### Notice
|
|
896
|
+
Due to [the limitation of PostHTML](https://github.com/posthtml/htmlnano/issues/99), htmlnano can't remove only the start tag or the end tag of an element. Currently, htmlnano only supports removing the following optional tags, as htmlnano can remove their start tag and end tag at the same time:
|
|
897
|
+
|
|
898
|
+
- `html`
|
|
899
|
+
- `head`
|
|
900
|
+
- `body`
|
|
901
|
+
- `colgroup`
|
|
902
|
+
- `tbody`
|
|
903
|
+
|
|
776
904
|
## Contribute
|
|
777
905
|
Since the minifier is modular, it's very easy to add new modules:
|
|
778
906
|
|
package/lib/helpers.js
CHANGED
|
@@ -8,35 +8,17 @@ exports.isComment = isComment;
|
|
|
8
8
|
exports.isConditionalComment = isConditionalComment;
|
|
9
9
|
exports.isStyleNode = isStyleNode;
|
|
10
10
|
exports.extractCssFromStyleNode = extractCssFromStyleNode;
|
|
11
|
-
|
|
12
|
-
function _createForOfIteratorHelper(o, allowArrayLike) { var it; if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
|
|
13
|
-
|
|
14
|
-
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
15
|
-
|
|
16
|
-
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
|
|
17
|
-
|
|
18
|
-
var ampBoilerplateAttributes = ['amp-boilerplate', 'amp4ads-boilerplate', 'amp4email-boilerplate'];
|
|
11
|
+
const ampBoilerplateAttributes = ['amp-boilerplate', 'amp4ads-boilerplate', 'amp4email-boilerplate'];
|
|
19
12
|
|
|
20
13
|
function isAmpBoilerplate(node) {
|
|
21
14
|
if (!node.attrs) {
|
|
22
15
|
return false;
|
|
23
16
|
}
|
|
24
17
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
30
|
-
var attr = _step.value;
|
|
31
|
-
|
|
32
|
-
if (attr in node.attrs) {
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
18
|
+
for (const attr of ampBoilerplateAttributes) {
|
|
19
|
+
if (attr in node.attrs) {
|
|
20
|
+
return true;
|
|
35
21
|
}
|
|
36
|
-
} catch (err) {
|
|
37
|
-
_iterator.e(err);
|
|
38
|
-
} finally {
|
|
39
|
-
_iterator.f();
|
|
40
22
|
}
|
|
41
23
|
|
|
42
24
|
return false;
|
package/lib/htmlnano.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports
|
|
6
|
+
exports.default = void 0;
|
|
7
7
|
|
|
8
8
|
var _posthtml = _interopRequireDefault(require("posthtml"));
|
|
9
9
|
|
|
@@ -13,58 +13,28 @@ var _ampSafe = _interopRequireDefault(require("./presets/ampSafe"));
|
|
|
13
13
|
|
|
14
14
|
var _max = _interopRequireDefault(require("./presets/max"));
|
|
15
15
|
|
|
16
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {
|
|
16
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
17
17
|
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
21
|
-
|
|
22
|
-
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
23
|
-
|
|
24
|
-
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
|
|
25
|
-
|
|
26
|
-
function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
|
|
27
|
-
|
|
28
|
-
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
29
|
-
|
|
30
|
-
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
|
|
31
|
-
|
|
32
|
-
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
|
|
33
|
-
|
|
34
|
-
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
35
|
-
|
|
36
|
-
function htmlnano() {
|
|
37
|
-
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
38
|
-
var preset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _safe["default"];
|
|
18
|
+
function htmlnano(options = {}, preset = _safe.default) {
|
|
39
19
|
return function minifier(tree) {
|
|
40
|
-
options =
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
|
|
45
|
-
moduleName = _Object$entries$_i[0],
|
|
46
|
-
moduleOptions = _Object$entries$_i[1];
|
|
20
|
+
options = { ...preset,
|
|
21
|
+
...options
|
|
22
|
+
};
|
|
23
|
+
let promise = Promise.resolve(tree);
|
|
47
24
|
|
|
25
|
+
for (const [moduleName, moduleOptions] of Object.entries(options)) {
|
|
48
26
|
if (!moduleOptions) {
|
|
49
27
|
// The module is disabled
|
|
50
|
-
|
|
28
|
+
continue;
|
|
51
29
|
}
|
|
52
30
|
|
|
53
|
-
if (_safe
|
|
31
|
+
if (_safe.default[moduleName] === undefined) {
|
|
54
32
|
throw new Error('Module "' + moduleName + '" is not defined');
|
|
55
33
|
}
|
|
56
34
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
promise = promise.then(function (tree) {
|
|
60
|
-
return module["default"](tree, options, moduleOptions);
|
|
61
|
-
});
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
for (var _i = 0, _Object$entries = Object.entries(options); _i < _Object$entries.length; _i++) {
|
|
65
|
-
var _ret = _loop();
|
|
35
|
+
let module = require('./modules/' + moduleName);
|
|
66
36
|
|
|
67
|
-
|
|
37
|
+
promise = promise.then(tree => module.default(tree, options, moduleOptions));
|
|
68
38
|
}
|
|
69
39
|
|
|
70
40
|
return promise;
|
|
@@ -72,13 +42,13 @@ function htmlnano() {
|
|
|
72
42
|
}
|
|
73
43
|
|
|
74
44
|
htmlnano.process = function (html, options, preset, postHtmlOptions) {
|
|
75
|
-
return (0, _posthtml
|
|
45
|
+
return (0, _posthtml.default)([htmlnano(options, preset)]).process(html, postHtmlOptions);
|
|
76
46
|
};
|
|
77
47
|
|
|
78
48
|
htmlnano.presets = {
|
|
79
|
-
safe: _safe
|
|
80
|
-
ampSafe: _ampSafe
|
|
81
|
-
max: _max
|
|
49
|
+
safe: _safe.default,
|
|
50
|
+
ampSafe: _ampSafe.default,
|
|
51
|
+
max: _max.default
|
|
82
52
|
};
|
|
83
53
|
var _default = htmlnano;
|
|
84
|
-
exports
|
|
54
|
+
exports.default = _default;
|
|
@@ -3,44 +3,27 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports
|
|
6
|
+
exports.default = collapseAttributeWhitespace;
|
|
7
7
|
exports.attributesWithLists = void 0;
|
|
8
|
-
|
|
9
|
-
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
|
|
10
|
-
|
|
11
|
-
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
12
|
-
|
|
13
|
-
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
14
|
-
|
|
15
|
-
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
|
|
16
|
-
|
|
17
|
-
function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
|
|
18
|
-
|
|
19
|
-
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
20
|
-
|
|
21
|
-
var attributesWithLists = new Set(['class', 'rel', 'ping']);
|
|
8
|
+
const attributesWithLists = new Set(['class', 'rel', 'ping']);
|
|
22
9
|
/** Collapse whitespaces inside list-like attributes (e.g. class, rel) */
|
|
23
10
|
|
|
24
11
|
exports.attributesWithLists = attributesWithLists;
|
|
25
12
|
|
|
26
13
|
function collapseAttributeWhitespace(tree) {
|
|
27
|
-
tree.walk(
|
|
14
|
+
tree.walk(node => {
|
|
28
15
|
if (!node.attrs) {
|
|
29
16
|
return node;
|
|
30
17
|
}
|
|
31
18
|
|
|
32
|
-
Object.entries(node.attrs).forEach(
|
|
33
|
-
|
|
34
|
-
attrName = _ref2[0],
|
|
35
|
-
attrValue = _ref2[1];
|
|
36
|
-
|
|
37
|
-
var attrNameLower = attrName.toLowerCase();
|
|
19
|
+
Object.entries(node.attrs).forEach(([attrName, attrValue]) => {
|
|
20
|
+
const attrNameLower = attrName.toLowerCase();
|
|
38
21
|
|
|
39
22
|
if (!attributesWithLists.has(attrNameLower)) {
|
|
40
23
|
return;
|
|
41
24
|
}
|
|
42
25
|
|
|
43
|
-
|
|
26
|
+
const newAttrValue = attrValue.replace(/\s+/g, ' ').trim();
|
|
44
27
|
node.attrs[attrName] = newAttrValue;
|
|
45
28
|
});
|
|
46
29
|
return node;
|
|
@@ -3,18 +3,16 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports
|
|
6
|
+
exports.default = collapseBooleanAttributes;
|
|
7
7
|
// Source: https://github.com/kangax/html-minifier/issues/63
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const htmlBooleanAttributes = new Set(['allowfullscreen', 'allowpaymentrequest', 'allowtransparency', 'async', 'autofocus', 'autoplay', 'checked', 'compact', 'controls', 'declare', 'default', 'defaultchecked', 'defaultmuted', 'defaultselected', 'defer', 'disabled', 'enabled', 'formnovalidate', 'hidden', 'indeterminate', 'inert', 'ismap', 'itemscope', 'loop', 'multiple', 'muted', 'nohref', 'noresize', 'noshade', 'novalidate', 'nowrap', 'open', 'pauseonexit', 'readonly', 'required', 'reversed', 'scoped', 'seamless', 'selected', 'sortable', 'truespeed', 'typemustmatch', 'visible']);
|
|
9
|
+
const amphtmlBooleanAttributes = new Set(['⚡', 'amp', '⚡4ads', 'amp4ads', '⚡4email', 'amp4email', 'amp-custom', 'amp-boilerplate', 'amp4ads-boilerplate', 'amp4email-boilerplate', 'allow-blocked-ranges', 'amp-access-hide', 'amp-access-template', 'amp-keyframes', 'animate', 'arrows', 'data-block-on-consent', 'data-enable-refresh', 'data-multi-size', 'date-template', 'disable-double-tap', 'disable-session-states', 'disableremoteplayback', 'dots', 'expand-single-section', 'expanded', 'fallback', 'first', 'fullscreen', 'inline', 'lightbox', 'noaudio', 'noautoplay', 'noloading', 'once', 'open-after-clear', 'open-after-select', 'open-button', 'placeholder', 'preload', 'reset-on-refresh', 'reset-on-resize', 'resizable', 'rotate-to-fullscreen', 'second', 'standalone', 'stereo', 'submit-error', 'submit-success', 'submitting', 'subscriptions-actions', 'subscriptions-dialog']);
|
|
10
10
|
|
|
11
11
|
function collapseBooleanAttributes(tree, options, moduleOptions) {
|
|
12
12
|
tree.match({
|
|
13
13
|
attrs: true
|
|
14
|
-
},
|
|
15
|
-
for (
|
|
16
|
-
var attrName = _Object$keys[_i];
|
|
17
|
-
|
|
14
|
+
}, node => {
|
|
15
|
+
for (const attrName of Object.keys(node.attrs)) {
|
|
18
16
|
if (!node.tag) {
|
|
19
17
|
continue;
|
|
20
18
|
}
|
|
@@ -3,33 +3,33 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports
|
|
6
|
+
exports.default = collapseWhitespace;
|
|
7
7
|
|
|
8
8
|
var _helpers = require("../helpers");
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const noWhitespaceCollapseElements = new Set(['script', 'style', 'pre', 'textarea']);
|
|
11
|
+
const noTrimWhitespacesArroundElements = new Set([// non-empty tags that will maintain whitespace around them
|
|
12
12
|
'a', 'abbr', 'acronym', 'b', 'bdi', 'bdo', 'big', 'button', 'cite', 'code', 'del', 'dfn', 'em', 'font', 'i', 'ins', 'kbd', 'label', 'mark', 'math', 'nobr', 'object', 'q', 'rp', 'rt', 'rtc', 'ruby', 's', 'samp', 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'svg', 'textarea', 'time', 'tt', 'u', 'var', // self-closing tags that will maintain whitespace around them
|
|
13
13
|
'comment', 'img', 'input', 'wbr']);
|
|
14
|
-
|
|
14
|
+
const noTrimWhitespacesInsideElements = new Set([// non-empty tags that will maintain whitespace within them
|
|
15
15
|
'a', 'abbr', 'acronym', 'b', 'big', 'del', 'em', 'font', 'i', 'ins', 'kbd', 'mark', 'nobr', 'rp', 's', 'samp', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'time', 'tt', 'u', 'var']);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
const whitespacePattern = /\s{1,}/g;
|
|
17
|
+
const onlyWhitespacePattern = /^\s+$/;
|
|
18
|
+
const NONE = '';
|
|
19
|
+
const SINGLE_SPACE = ' ';
|
|
20
|
+
const validOptions = ['all', 'aggressive', 'conservative'];
|
|
21
21
|
/** Collapses redundant whitespaces */
|
|
22
22
|
|
|
23
23
|
function collapseWhitespace(tree, options, collapseType, tag) {
|
|
24
24
|
collapseType = validOptions.includes(collapseType) ? collapseType : 'conservative';
|
|
25
|
-
tree.forEach(
|
|
25
|
+
tree.forEach((node, index) => {
|
|
26
26
|
if (typeof node === 'string' && !(0, _helpers.isComment)(node)) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
const prevNode = tree[index - 1];
|
|
28
|
+
const nextNode = tree[index + 1];
|
|
29
|
+
const prevNodeTag = prevNode && prevNode.tag;
|
|
30
|
+
const nextNodeTag = nextNode && nextNode.tag;
|
|
31
|
+
const isTopLevel = !tag || tag === 'html' || tag === 'head';
|
|
32
|
+
const shouldTrim = collapseType === 'all' || isTopLevel ||
|
|
33
33
|
/*
|
|
34
34
|
* When collapseType is set to 'aggressive', and the tag is not inside 'noTrimWhitespacesInsideElements'.
|
|
35
35
|
* the first & last space inside the tag will be trimmed
|
|
@@ -38,7 +38,7 @@ function collapseWhitespace(tree, options, collapseType, tag) {
|
|
|
38
38
|
node = collapseRedundantWhitespaces(node, collapseType, shouldTrim, tag, prevNodeTag, nextNodeTag);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
const isAllowCollapseWhitespace = !noWhitespaceCollapseElements.has(node.tag);
|
|
42
42
|
|
|
43
43
|
if (node.content && node.content.length && isAllowCollapseWhitespace) {
|
|
44
44
|
node.content = collapseWhitespace(node.content, options, collapseType, node.tag);
|
|
@@ -49,12 +49,7 @@ function collapseWhitespace(tree, options, collapseType, tag) {
|
|
|
49
49
|
return tree;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
function collapseRedundantWhitespaces(text, collapseType) {
|
|
53
|
-
var shouldTrim = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
54
|
-
var currentTag = arguments.length > 3 ? arguments[3] : undefined;
|
|
55
|
-
var prevNodeTag = arguments.length > 4 ? arguments[4] : undefined;
|
|
56
|
-
var nextNodeTag = arguments.length > 5 ? arguments[5] : undefined;
|
|
57
|
-
|
|
52
|
+
function collapseRedundantWhitespaces(text, collapseType, shouldTrim = false, currentTag, prevNodeTag, nextNodeTag) {
|
|
58
53
|
if (!text || text.length === 0) {
|
|
59
54
|
return NONE;
|
|
60
55
|
}
|
|
@@ -66,7 +61,7 @@ function collapseRedundantWhitespaces(text, collapseType) {
|
|
|
66
61
|
if (onlyWhitespacePattern.test(text)) {
|
|
67
62
|
// "text" only contains whitespaces. Only trim when both prevNodeTag & nextNodeTag are not "noTrimWhitespacesArroundElement"
|
|
68
63
|
// Otherwise the required ONE whitespace will be trimmed
|
|
69
|
-
if (!noTrimWhitespacesArroundElements.has(prevNodeTag)
|
|
64
|
+
if (!noTrimWhitespacesArroundElements.has(prevNodeTag) || !noTrimWhitespacesArroundElements.has(nextNodeTag)) {
|
|
70
65
|
text = text.trim();
|
|
71
66
|
}
|
|
72
67
|
} else {
|
package/lib/modules/custom.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports
|
|
6
|
+
exports.default = custom;
|
|
7
7
|
|
|
8
8
|
/** Meta-module that runs custom modules */
|
|
9
9
|
function custom(tree, options, customModules) {
|
|
@@ -15,7 +15,7 @@ function custom(tree, options, customModules) {
|
|
|
15
15
|
customModules = [customModules];
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
customModules.forEach(
|
|
18
|
+
customModules.forEach(customModule => {
|
|
19
19
|
tree = customModule(tree, options);
|
|
20
20
|
});
|
|
21
21
|
return tree;
|
|
@@ -3,28 +3,28 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports
|
|
6
|
+
exports.default = collapseAttributeWhitespace;
|
|
7
7
|
|
|
8
8
|
var _collapseAttributeWhitespace = require("./collapseAttributeWhitespace");
|
|
9
9
|
|
|
10
10
|
/** Deduplicate values inside list-like attributes (e.g. class, rel) */
|
|
11
11
|
function collapseAttributeWhitespace(tree) {
|
|
12
|
-
tree.walk(
|
|
12
|
+
tree.walk(node => {
|
|
13
13
|
if (!node.attrs) {
|
|
14
14
|
return node;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
Object.keys(node.attrs).forEach(
|
|
18
|
-
|
|
17
|
+
Object.keys(node.attrs).forEach(attrName => {
|
|
18
|
+
const attrNameLower = attrName.toLowerCase();
|
|
19
19
|
|
|
20
20
|
if (!_collapseAttributeWhitespace.attributesWithLists.has(attrNameLower)) {
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
attrValues.forEach(
|
|
24
|
+
const attrValues = node.attrs[attrName].split(/\s/);
|
|
25
|
+
const uniqeAttrValues = new Set();
|
|
26
|
+
let deduplicatedAttrValues = [];
|
|
27
|
+
attrValues.forEach(attrValue => {
|
|
28
28
|
if (!attrValue) {
|
|
29
29
|
// Keep whitespaces
|
|
30
30
|
deduplicatedAttrValues.push('');
|
|
@@ -3,31 +3,31 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports
|
|
6
|
+
exports.default = mergeScripts;
|
|
7
7
|
|
|
8
8
|
/* Merge multiple <script> into one */
|
|
9
9
|
function mergeScripts(tree) {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
let scriptNodesIndex = {};
|
|
11
|
+
let scriptSrcIndex = 1;
|
|
12
12
|
tree.match({
|
|
13
13
|
tag: 'script'
|
|
14
|
-
},
|
|
15
|
-
|
|
14
|
+
}, node => {
|
|
15
|
+
const nodeAttrs = node.attrs || {};
|
|
16
16
|
|
|
17
17
|
if (nodeAttrs.src) {
|
|
18
18
|
scriptSrcIndex++;
|
|
19
19
|
return node;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
const scriptType = nodeAttrs.type || 'text/javascript';
|
|
23
23
|
|
|
24
24
|
if (scriptType !== 'text/javascript' && scriptType !== 'application/javascript') {
|
|
25
25
|
return node;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
const scriptKey = JSON.stringify({
|
|
29
29
|
id: nodeAttrs.id,
|
|
30
|
-
|
|
30
|
+
class: nodeAttrs.class,
|
|
31
31
|
type: scriptType,
|
|
32
32
|
defer: nodeAttrs.defer !== undefined,
|
|
33
33
|
async: nodeAttrs.async !== undefined,
|
|
@@ -42,11 +42,10 @@ function mergeScripts(tree) {
|
|
|
42
42
|
return node;
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
var scriptContent = (scriptNode.content || []).join(' ');
|
|
45
|
+
for (const scriptNodes of Object.values(scriptNodesIndex)) {
|
|
46
|
+
let lastScriptNode = scriptNodes.pop();
|
|
47
|
+
scriptNodes.reverse().forEach(scriptNode => {
|
|
48
|
+
let scriptContent = (scriptNode.content || []).join(' ');
|
|
50
49
|
scriptContent = scriptContent.trim();
|
|
51
50
|
|
|
52
51
|
if (scriptContent.slice(-1) !== ';') {
|
|
@@ -58,10 +57,6 @@ function mergeScripts(tree) {
|
|
|
58
57
|
scriptNode.tag = false;
|
|
59
58
|
scriptNode.content = [];
|
|
60
59
|
});
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
for (var _i = 0, _Object$values = Object.values(scriptNodesIndex); _i < _Object$values.length; _i++) {
|
|
64
|
-
_loop();
|
|
65
60
|
}
|
|
66
61
|
|
|
67
62
|
return tree;
|