htmlnano 0.2.4 → 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/CHANGELOG.md CHANGED
@@ -2,49 +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
+
5
12
 
6
13
  ## [0.2.4] - 2019-07-11
7
- ## Fixed
14
+ ### Fixed
8
15
  - Remove crossorigin from boolean attribute [#78], [#79].
9
16
  - Disable SVGO plugin convertShapeToPath in safe preset [#76].
10
17
 
11
18
 
12
19
  ## [0.2.3] - 2019-02-14
13
- ## Fixed
20
+ ### Fixed
14
21
  - Keep `<g>` in SVG by default [#71].
15
22
 
16
23
 
17
24
  ## [0.2.2] - 2019-01-03
18
- ## Added
25
+ ### Added
19
26
  - `removeUnusedCss` module [#36].
20
27
 
21
- ## Fixed
28
+ ### Fixed
22
29
  - Bug when `tag === false` [#66].
23
30
  - Add `crossorigin` to boolean attributes [#67].
24
31
 
25
32
 
26
33
  ## [0.2.1] - 2018-12-01
27
- ## Fixed
34
+ ### Fixed
28
35
  - Disable JS minifying on AMP pages [#65].
29
36
 
30
37
  ## [0.2.0] - 2018-09-14
31
- ## Breaking changes
38
+ ### Breaking changes
32
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:
33
40
  * [htmlnano docs](https://github.com/posthtml/htmlnano#minifycss)
34
41
  * [cssnano docs](https://cssnano.co/guides/presets)
35
42
  * Diff of commit [979f2c](https://github.com/posthtml/htmlnano/commit/979f2c821892c9e979e8b85f74ed0394330fceaf) with the changes of related docs.
36
43
 
37
- ## Added
44
+ ### Added
38
45
  - Add presets [#64].
39
46
  - Add `collapseAttributeWhitespace` module for collapsing spaces in list-like attributes [#25].
40
47
  - Add `deduplicateAttributeValues` module for de-duplicating values in list-like attributes [#39].
41
48
  - Better support for AMP pages [#59].
42
49
  - Collapse whitespaces between top-level tags [#24].
43
50
 
44
- ## Changed
51
+ ### Changed
45
52
  - Improve whitespace normalization using `normalize-html-whitespace` [#21].
46
53
 
47
- ## Fixed
54
+ ### Fixed
48
55
  - Don't collapse `visible="false"` attributes in A-Frame pages [#62].
49
56
 
50
57
 
@@ -122,6 +129,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
122
129
 
123
130
 
124
131
 
132
+ [0.2.5]: https://github.com/posthtml/htmlnano/compare/0.2.4...0.2.5
125
133
  [0.2.4]: https://github.com/posthtml/htmlnano/compare/0.2.3...0.2.4
126
134
  [0.2.3]: https://github.com/posthtml/htmlnano/compare/0.2.2...0.2.3
127
135
  [0.2.2]: https://github.com/posthtml/htmlnano/compare/0.2.1...0.2.2
@@ -139,6 +147,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
139
147
  [0.1.1]: https://github.com/posthtml/htmlnano/compare/0.1.0...0.1.1
140
148
 
141
149
 
150
+ [#84]: https://github.com/posthtml/htmlnano/issues/84
151
+ [#80]: https://github.com/posthtml/htmlnano/issues/80
142
152
  [#79]: https://github.com/posthtml/htmlnano/issues/79
143
153
  [#78]: https://github.com/posthtml/htmlnano/issues/78
144
154
  [#76]: https://github.com/posthtml/htmlnano/issues/76
package/README.md CHANGED
@@ -76,8 +76,7 @@ const posthtmlPlugins = [
76
76
  require('htmlnano')(options)
77
77
  ];
78
78
 
79
- // "preset" arg might be skipped (see "Presets" section below for more info)
80
- posthtml(posthtmlPlugins, preset)
79
+ posthtml(posthtmlPlugins)
81
80
  .process(html)
82
81
  .then(function (result) {
83
82
  // result.html is minified
@@ -273,7 +272,11 @@ Minified:
273
272
  ```
274
273
 
275
274
  ### removeUnusedCss
276
- Removes unused CSS with [uncss](https://github.com/uncss/uncss) inside `<style>` tags.
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
277
280
 
278
281
  ##### Options
279
282
  See [the documentation of uncss](https://github.com/uncss/uncss) for all supported options.
@@ -293,6 +296,30 @@ The following uncss options are ignored if passed to the module:
293
296
  - `ignoreSheets`
294
297
  - `raw`
295
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
+
296
323
  ##### Example
297
324
  Source:
298
325
  ```html
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 = (0, _objectAssign2.default)({}, preset, options);
30
+ options = Object.assign({}, preset, options);
35
31
  var promise = Promise.resolve(tree);
36
32
 
37
33
  var _loop = function _loop(moduleName) {
@@ -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] = [];
@@ -14,35 +14,25 @@ var _uncss = require('uncss');
14
14
 
15
15
  var _uncss2 = _interopRequireDefault(_uncss);
16
16
 
17
+ var _purgecss = require('purgecss');
18
+
19
+ var _purgecss2 = _interopRequireDefault(_purgecss);
20
+
17
21
  var _posthtmlRender = require('posthtml-render');
18
22
 
19
23
  var _posthtmlRender2 = _interopRequireDefault(_posthtmlRender);
20
24
 
21
25
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
22
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
+
23
29
  // These options must be set and shouldn't be overriden to ensure uncss doesn't look at linked stylesheets.
24
30
  var uncssOptions = {
25
31
  ignoreSheets: [/\s*/],
26
32
  stylesheets: []
27
33
  };
28
34
 
29
- /** Remove unused CSS using uncss */
30
- function removeUnusedCss(tree, options, uncssOptions) {
31
- var promises = [];
32
- var html = (0, _posthtmlRender2.default)(tree);
33
- tree.walk(function (node) {
34
- if ((0, _helpers.isStyleNode)(node)) {
35
- promises.push(processStyleNode(html, node, uncssOptions));
36
- }
37
- return node;
38
- });
39
-
40
- return Promise.all(promises).then(function () {
41
- return tree;
42
- });
43
- }
44
-
45
- function processStyleNode(html, styleNode, uncssOptions) {
35
+ function processStyleNodeUnCSS(html, styleNode, uncssOptions) {
46
36
  var css = (0, _helpers.extractCssFromStyleNode)(styleNode);
47
37
 
48
38
  return runUncss(html, css, uncssOptions).then(function (css) {
@@ -72,4 +62,86 @@ function runUncss(html, css, userOptions) {
72
62
  resolve(output);
73
63
  });
74
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
+ });
75
147
  }
@@ -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 = (0, _objectAssign2.default)({}, _safe2.default, {
18
+ exports.default = Object.assign({}, _safe2.default, {
23
19
  collapseBooleanAttributes: {
24
20
  amphtml: true
25
21
  },
@@ -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
  * Maximal minification (might break some pages)
21
17
  */
22
- exports.default = (0, _objectAssign2.default)({}, _safe2.default, {
18
+ exports.default = Object.assign({}, _safe2.default, {
23
19
  collapseWhitespace: 'all',
24
20
  removeComments: 'all',
25
21
  removeRedundantAttributes: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "htmlnano",
3
- "version": "0.2.4",
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>",
@@ -34,23 +34,23 @@
34
34
  "dependencies": {
35
35
  "cssnano": "^4.1.10",
36
36
  "normalize-html-whitespace": "^1.0.0",
37
- "object-assign": "^4.0.1",
38
- "posthtml": "^0.11.4",
37
+ "posthtml": "^0.12.0",
39
38
  "posthtml-render": "^1.1.5",
40
- "svgo": "^1.2.2",
41
- "terser": "^4.1.2",
42
- "uncss": "^0.17.0"
39
+ "purgecss": "^1.4.0",
40
+ "svgo": "^1.3.2",
41
+ "terser": "^4.3.9",
42
+ "uncss": "^0.17.2"
43
43
  },
44
44
  "devDependencies": {
45
45
  "babel-cli": "^6.26.0",
46
46
  "babel-core": "^6.26.3",
47
- "babel-eslint": "^10.0.2",
47
+ "babel-eslint": "^10.0.3",
48
48
  "babel-preset-env": "^1.7.0",
49
- "eslint": "^6.0.1",
50
- "expect": "^24.8.0",
51
- "mocha": "^6.1.0",
52
- "release-it": "^12.3.3",
53
- "rimraf": "^2.6.3"
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"
54
54
  },
55
55
  "repository": {
56
56
  "type": "git",
package/test.js CHANGED
@@ -1,13 +1,13 @@
1
1
  const htmlnano = require('./index');
2
2
  const fs = require('fs');
3
- const safePreset = require('./lib/presets/safe').default;
3
+ const preset = require('./lib/presets/max').default;
4
4
 
5
5
  const options = {};
6
6
 
7
7
  const html = fs.readFileSync('./test.html', 'utf8');
8
8
 
9
9
  htmlnano
10
- .process(html, options)
10
+ .process(html, options, preset)
11
11
  .then(function (result) {
12
12
  console.log(result.html);
13
13
  })