htmlnano 0.2.4 → 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.
@@ -1,75 +1,127 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
3
  Object.defineProperty(exports, "__esModule", {
4
- value: true
4
+ value: true
5
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
6
  exports.default = removeUnusedCss;
10
7
 
11
- var _helpers = require('../helpers');
8
+ var _helpers = require("../helpers");
12
9
 
13
- var _uncss = require('uncss');
10
+ var _uncss = _interopRequireDefault(require("uncss"));
14
11
 
15
- var _uncss2 = _interopRequireDefault(_uncss);
12
+ var _purgecss = _interopRequireDefault(require("purgecss"));
16
13
 
17
- var _posthtmlRender = require('posthtml-render');
18
-
19
- var _posthtmlRender2 = _interopRequireDefault(_posthtmlRender);
14
+ var _posthtmlRender = _interopRequireDefault(require("posthtml-render"));
20
15
 
21
16
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
22
17
 
23
18
  // These options must be set and shouldn't be overriden to ensure uncss doesn't look at linked stylesheets.
24
- var uncssOptions = {
25
- ignoreSheets: [/\s*/],
26
- stylesheets: []
19
+ const uncssOptions = {
20
+ ignoreSheets: [/\s*/],
21
+ stylesheets: []
27
22
  };
28
23
 
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
- });
24
+ function processStyleNodeUnCSS(html, styleNode, uncssOptions) {
25
+ const css = (0, _helpers.extractCssFromStyleNode)(styleNode);
26
+ return runUncss(html, css, uncssOptions).then(css => {
27
+ // uncss may have left some style tags empty
28
+ if (css.trim().length === 0) {
29
+ styleNode.tag = false;
30
+ styleNode.content = [];
31
+ return;
32
+ }
39
33
 
40
- return Promise.all(promises).then(function () {
41
- return tree;
42
- });
34
+ styleNode.content = [css];
35
+ });
43
36
  }
44
37
 
45
- function processStyleNode(html, styleNode, uncssOptions) {
46
- var css = (0, _helpers.extractCssFromStyleNode)(styleNode);
47
-
48
- return runUncss(html, css, uncssOptions).then(function (css) {
49
- // uncss may have left some style tags empty
50
- if (css.trim().length === 0) {
51
- styleNode.tag = false;
52
- styleNode.content = [];
53
- return;
54
- }
55
- styleNode.content = [css];
38
+ function runUncss(html, css, userOptions) {
39
+ if (typeof userOptions !== 'object') {
40
+ userOptions = {};
41
+ }
42
+
43
+ const options = { ...userOptions,
44
+ ...uncssOptions
45
+ };
46
+ return new Promise((resolve, reject) => {
47
+ options.raw = css;
48
+ (0, _uncss.default)(html, options, (error, output) => {
49
+ if (error) {
50
+ reject(error);
51
+ return;
52
+ }
53
+
54
+ resolve(output);
56
55
  });
56
+ });
57
57
  }
58
58
 
59
- function runUncss(html, css, userOptions) {
60
- if ((typeof userOptions === 'undefined' ? 'undefined' : _typeof(userOptions)) !== 'object') {
61
- userOptions = {};
59
+ const purgeFromHtml = function (tree) {
60
+ // content is not used as we can directly used the parsed HTML,
61
+ // making the process faster
62
+ const selectors = [];
63
+ tree.walk(node => {
64
+ const classes = node.attrs && node.attrs.class && node.attrs.class.split(' ') || [];
65
+ const ids = node.attrs && node.attrs.id && node.attrs.id.split(' ') || [];
66
+ selectors.push(...classes, ...ids);
67
+ node.tag && selectors.push(node.tag);
68
+ return node;
69
+ });
70
+ return () => selectors;
71
+ };
72
+
73
+ function processStyleNodePurgeCSS(tree, styleNode, purgecssOptions) {
74
+ const css = (0, _helpers.extractCssFromStyleNode)(styleNode);
75
+ return runPurgecss(tree, css, purgecssOptions).then(css => {
76
+ if (css.trim().length === 0) {
77
+ styleNode.tag = false;
78
+ styleNode.content = [];
79
+ return;
62
80
  }
63
81
 
64
- var options = Object.assign({}, userOptions, uncssOptions);
65
- return new Promise(function (resolve, reject) {
66
- options.raw = css;
67
- (0, _uncss2.default)(html, options, function (error, output) {
68
- if (error) {
69
- reject(error);
70
- return;
71
- }
72
- resolve(output);
73
- });
74
- });
82
+ styleNode.content = [css];
83
+ });
84
+ }
85
+
86
+ function runPurgecss(tree, css, userOptions) {
87
+ if (typeof userOptions !== 'object') {
88
+ userOptions = {};
89
+ }
90
+
91
+ const options = { ...userOptions,
92
+ content: [{
93
+ raw: tree,
94
+ extension: 'html'
95
+ }],
96
+ css: [{
97
+ raw: css,
98
+ extension: 'css'
99
+ }],
100
+ extractors: [{
101
+ extractor: purgeFromHtml(tree),
102
+ extensions: ['html']
103
+ }]
104
+ };
105
+ return new _purgecss.default().purge(options).then(result => {
106
+ return result[0].css;
107
+ });
108
+ }
109
+ /** Remove unused CSS */
110
+
111
+
112
+ function removeUnusedCss(tree, options, userOptions) {
113
+ const promises = [];
114
+ const html = userOptions.tool !== 'purgeCSS' && (0, _posthtmlRender.default)(tree);
115
+ tree.walk(node => {
116
+ if ((0, _helpers.isStyleNode)(node)) {
117
+ if (userOptions.tool === 'purgeCSS') {
118
+ promises.push(processStyleNodePurgeCSS(tree, node, userOptions));
119
+ } else {
120
+ promises.push(processStyleNodeUnCSS(html, node, userOptions));
121
+ }
122
+ }
123
+
124
+ return node;
125
+ });
126
+ return Promise.all(promises).then(() => tree);
75
127
  }
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = sortAttributes;
7
+
8
+ var _timsort = require("timsort");
9
+
10
+ const validOptions = new Set(['frequency', 'alphabetical']);
11
+
12
+ const processModuleOptions = options => {
13
+ if (options === true) return 'alphabetical';
14
+ return validOptions.has(options) ? options : false;
15
+ };
16
+
17
+ class AttributeTokenChain {
18
+ constructor() {
19
+ this.freqData = new Map(); // <attr, frequency>[]
20
+ }
21
+
22
+ addFromNodeAttrs(nodeAttrs) {
23
+ Object.keys(nodeAttrs).forEach(attrName => {
24
+ const attrNameLower = attrName.toLowerCase();
25
+
26
+ if (this.freqData.has(attrNameLower)) {
27
+ this.freqData.set(attrNameLower, this.freqData.get(attrNameLower) + 1);
28
+ } else {
29
+ this.freqData.set(attrNameLower, 1);
30
+ }
31
+ });
32
+ }
33
+
34
+ createSortOrder() {
35
+ let _sortOrder = [];
36
+
37
+ for (const item of this.freqData.entries()) {
38
+ _sortOrder.push(item);
39
+ }
40
+
41
+ (0, _timsort.sort)(_sortOrder, (a, b) => b[1] - a[1]);
42
+ this.sortOrder = _sortOrder.map(i => i[0]);
43
+ }
44
+
45
+ sortFromNodeAttrs(nodeAttrs) {
46
+ const newAttrs = {}; // Convert node.attrs attrName into lower case.
47
+
48
+ const loweredNodeAttrs = {};
49
+ Object.entries(nodeAttrs).forEach(([attrName, attrValue]) => {
50
+ loweredNodeAttrs[attrName.toLowerCase()] = attrValue;
51
+ });
52
+
53
+ if (!this.sortOrder) {
54
+ this.createSortOrder();
55
+ }
56
+
57
+ this.sortOrder.forEach(attrNameLower => {
58
+ // The attrName inside "sortOrder" has been lowered
59
+ if (loweredNodeAttrs[attrNameLower]) {
60
+ newAttrs[attrNameLower] = loweredNodeAttrs[attrNameLower];
61
+ }
62
+ });
63
+ return newAttrs;
64
+ }
65
+
66
+ }
67
+ /** Sort attibutes */
68
+
69
+
70
+ function sortAttributes(tree, options, moduleOptions) {
71
+ const sortType = processModuleOptions(moduleOptions);
72
+
73
+ if (sortType === 'alphabetical') {
74
+ return sortAttributesInAlphabeticalOrder(tree);
75
+ }
76
+
77
+ if (sortType === 'frequency') {
78
+ return sortAttributesByFrequency(tree);
79
+ } // Invalid configuration
80
+
81
+
82
+ return tree;
83
+ }
84
+
85
+ function sortAttributesInAlphabeticalOrder(tree) {
86
+ tree.walk(node => {
87
+ if (!node.attrs) {
88
+ return node;
89
+ }
90
+
91
+ const newAttrs = {};
92
+ Object.keys(node.attrs).sort((a, b) => typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b).forEach(attr => newAttrs[attr] = node.attrs[attr]);
93
+ node.attrs = newAttrs;
94
+ return node;
95
+ });
96
+ return tree;
97
+ }
98
+
99
+ function sortAttributesByFrequency(tree) {
100
+ const tokenchain = new AttributeTokenChain(); // Traverse through tree to get frequency
101
+
102
+ tree.walk(node => {
103
+ if (!node.attrs) {
104
+ return node;
105
+ }
106
+
107
+ tokenchain.addFromNodeAttrs(node.attrs);
108
+ return node;
109
+ }); // Traverse through tree again, this time sort the attributes
110
+
111
+ tree.walk(node => {
112
+ if (!node.attrs) {
113
+ return node;
114
+ }
115
+
116
+ node.attrs = tokenchain.sortFromNodeAttrs(node.attrs);
117
+ return node;
118
+ });
119
+ return tree;
120
+ }
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = collapseAttributeWhitespace;
7
+
8
+ var _timsort = require("timsort");
9
+
10
+ var _collapseAttributeWhitespace = require("./collapseAttributeWhitespace");
11
+
12
+ // class, rel, ping
13
+ const validOptions = new Set(['frequency', 'alphabetical']);
14
+
15
+ const processModuleOptions = options => {
16
+ if (options === true) return 'alphabetical';
17
+ return validOptions.has(options) ? options : false;
18
+ };
19
+
20
+ class AttributeTokenChain {
21
+ constructor() {
22
+ this.freqData = new Map(); // <attrValue, frequency>[]
23
+ }
24
+
25
+ addFromNodeAttrsArray(attrValuesArray) {
26
+ attrValuesArray.forEach(attrValue => {
27
+ if (this.freqData.has(attrValue)) {
28
+ this.freqData.set(attrValue, this.freqData.get(attrValue) + 1);
29
+ } else {
30
+ this.freqData.set(attrValue, 1);
31
+ }
32
+ });
33
+ }
34
+
35
+ createSortOrder() {
36
+ let _sortOrder = [];
37
+
38
+ for (const item of this.freqData.entries()) {
39
+ _sortOrder.push(item);
40
+ }
41
+
42
+ (0, _timsort.sort)(_sortOrder, (a, b) => b[1] - a[1]);
43
+ this.sortOrder = _sortOrder.map(i => i[0]);
44
+ }
45
+
46
+ sortFromNodeAttrsArray(attrValuesArray) {
47
+ const resultArray = [];
48
+
49
+ if (!this.sortOrder) {
50
+ this.createSortOrder();
51
+ }
52
+
53
+ this.sortOrder.forEach(k => {
54
+ if (attrValuesArray.includes(k)) {
55
+ resultArray.push(k);
56
+ }
57
+ });
58
+ return resultArray;
59
+ }
60
+
61
+ }
62
+ /** Sort values inside list-like attributes (e.g. class, rel) */
63
+
64
+
65
+ function collapseAttributeWhitespace(tree, options, moduleOptions) {
66
+ const sortType = processModuleOptions(moduleOptions);
67
+
68
+ if (sortType === 'alphabetical') {
69
+ return sortAttributesWithListsInAlphabeticalOrder(tree);
70
+ }
71
+
72
+ if (sortType === 'frequency') {
73
+ return sortAttributesWithListsByFrequency(tree);
74
+ } // Invalid configuration
75
+
76
+
77
+ return tree;
78
+ }
79
+
80
+ function sortAttributesWithListsInAlphabeticalOrder(tree) {
81
+ tree.walk(node => {
82
+ if (!node.attrs) {
83
+ return node;
84
+ }
85
+
86
+ Object.keys(node.attrs).forEach(attrName => {
87
+ const attrNameLower = attrName.toLowerCase();
88
+
89
+ if (!_collapseAttributeWhitespace.attributesWithLists.has(attrNameLower)) {
90
+ return;
91
+ }
92
+
93
+ const attrValues = node.attrs[attrName].split(/\s/);
94
+ node.attrs[attrName] = attrValues.sort((a, b) => {
95
+ return typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b;
96
+ }).join(' ');
97
+ });
98
+ return node;
99
+ });
100
+ return tree;
101
+ }
102
+
103
+ function sortAttributesWithListsByFrequency(tree) {
104
+ const tokenChainObj = {}; // <attrNameLower: AttributeTokenChain>[]
105
+ // Traverse through tree to get frequency
106
+
107
+ tree.walk(node => {
108
+ if (!node.attrs) {
109
+ return node;
110
+ }
111
+
112
+ Object.entries(node.attrs).forEach(([attrName, attrValues]) => {
113
+ const attrNameLower = attrName.toLowerCase();
114
+
115
+ if (!_collapseAttributeWhitespace.attributesWithLists.has(attrNameLower)) {
116
+ return;
117
+ }
118
+
119
+ tokenChainObj[attrNameLower] = tokenChainObj[attrNameLower] || new AttributeTokenChain();
120
+ tokenChainObj[attrNameLower].addFromNodeAttrsArray(attrValues.split(/\s/));
121
+ });
122
+ return node;
123
+ }); // Traverse through tree again, this time sort the attribute values
124
+
125
+ tree.walk(node => {
126
+ if (!node.attrs) {
127
+ return node;
128
+ }
129
+
130
+ Object.entries(node.attrs).forEach(([attrName, attrValues]) => {
131
+ const attrNameLower = attrName.toLowerCase();
132
+
133
+ if (!_collapseAttributeWhitespace.attributesWithLists.has(attrNameLower)) {
134
+ return;
135
+ }
136
+
137
+ if (tokenChainObj[attrNameLower]) {
138
+ node.attrs[attrName] = tokenChainObj[attrNameLower].sortFromNodeAttrsArray(attrValues.split(/\s/)).join(' ');
139
+ }
140
+ });
141
+ return node;
142
+ });
143
+ }
@@ -1,27 +1,26 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
3
  Object.defineProperty(exports, "__esModule", {
4
- value: true
4
+ value: true
5
5
  });
6
+ exports.default = void 0;
6
7
 
7
- var _objectAssign = require('object-assign');
8
-
9
- var _objectAssign2 = _interopRequireDefault(_objectAssign);
10
-
11
- var _safe = require('./safe');
12
-
13
- var _safe2 = _interopRequireDefault(_safe);
8
+ var _safe = _interopRequireDefault(require("./safe"));
14
9
 
15
10
  function _interopRequireDefault(obj) {
16
- return obj && obj.__esModule ? obj : { default: obj };
11
+ return obj && obj.__esModule ? obj : {
12
+ default: obj
13
+ };
17
14
  }
18
-
19
15
  /**
20
16
  * A safe preset for AMP pages (https://www.ampproject.org)
21
17
  */
22
- exports.default = (0, _objectAssign2.default)({}, _safe2.default, {
23
- collapseBooleanAttributes: {
24
- amphtml: true
25
- },
26
- minifyJs: false
27
- });
18
+
19
+
20
+ var _default = { ..._safe.default,
21
+ collapseBooleanAttributes: {
22
+ amphtml: true
23
+ },
24
+ minifyJs: false
25
+ };
26
+ exports.default = _default;
@@ -1,31 +1,33 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
3
  Object.defineProperty(exports, "__esModule", {
4
- value: true
4
+ value: true
5
5
  });
6
+ exports.default = void 0;
6
7
 
7
- var _objectAssign = require('object-assign');
8
-
9
- var _objectAssign2 = _interopRequireDefault(_objectAssign);
10
-
11
- var _safe = require('./safe');
12
-
13
- var _safe2 = _interopRequireDefault(_safe);
8
+ var _safe = _interopRequireDefault(require("./safe"));
14
9
 
15
10
  function _interopRequireDefault(obj) {
16
- return obj && obj.__esModule ? obj : { default: obj };
11
+ return obj && obj.__esModule ? obj : {
12
+ default: obj
13
+ };
17
14
  }
18
-
19
15
  /**
20
16
  * Maximal minification (might break some pages)
21
17
  */
22
- exports.default = (0, _objectAssign2.default)({}, _safe2.default, {
23
- collapseWhitespace: 'all',
24
- removeComments: 'all',
25
- removeRedundantAttributes: true,
26
- removeUnusedCss: {},
27
- minifyCss: {
28
- preset: 'default'
29
- },
30
- minifySvg: {}
31
- });
18
+
19
+
20
+ var _default = { ..._safe.default,
21
+ collapseWhitespace: 'all',
22
+ removeComments: 'all',
23
+ removeAttributeQuotes: true,
24
+ removeRedundantAttributes: true,
25
+ removeUnusedCss: {},
26
+ minifyCss: {
27
+ preset: 'default'
28
+ },
29
+ minifySvg: {},
30
+ minifyConditionalComments: true,
31
+ removeOptionalTags: true
32
+ };
33
+ exports.default = _default;
@@ -1,31 +1,44 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
3
  Object.defineProperty(exports, "__esModule", {
4
- value: true
4
+ value: true
5
5
  });
6
+ exports.default = void 0;
6
7
  /**
7
8
  * Minify HTML in a safe way without breaking anything.
8
9
  */
9
- exports.default = {
10
- collapseAttributeWhitespace: true,
11
- collapseBooleanAttributes: {
12
- amphtml: false
13
- },
14
- collapseWhitespace: 'conservative',
15
- custom: [],
16
- deduplicateAttributeValues: true,
17
- mergeScripts: true,
18
- mergeStyles: true,
19
- removeUnusedCss: false,
20
- minifyCss: {
21
- preset: 'default'
22
- },
23
- minifyJs: {},
24
- minifyJson: {},
25
- minifySvg: {
26
- plugins: [{ collapseGroups: false }, { convertShapeToPath: false }]
27
- },
28
- removeEmptyAttributes: true,
29
- removeRedundantAttributes: false,
30
- removeComments: 'safe'
31
- };
10
+
11
+ var _default = {
12
+ sortAttributes: false,
13
+ collapseAttributeWhitespace: true,
14
+ collapseBooleanAttributes: {
15
+ amphtml: false
16
+ },
17
+ collapseWhitespace: 'conservative',
18
+ custom: [],
19
+ deduplicateAttributeValues: true,
20
+ mergeScripts: true,
21
+ mergeStyles: true,
22
+ removeUnusedCss: false,
23
+ minifyCss: {
24
+ preset: 'default'
25
+ },
26
+ minifyJs: {},
27
+ minifyJson: {},
28
+ minifySvg: {
29
+ plugins: [{
30
+ collapseGroups: false
31
+ }, {
32
+ convertShapeToPath: false
33
+ }]
34
+ },
35
+ minifyConditionalComments: false,
36
+ removeEmptyAttributes: true,
37
+ removeRedundantAttributes: false,
38
+ removeComments: 'safe',
39
+ removeAttributeQuotes: false,
40
+ sortAttributesWithLists: 'alphabetical',
41
+ minifyUrls: false,
42
+ removeOptionalTags: false
43
+ };
44
+ exports.default = _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "htmlnano",
3
- "version": "0.2.4",
3
+ "version": "0.2.8",
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>",
@@ -10,7 +10,7 @@
10
10
  "lint": "eslint *.js lib/*.es6 lib/modules/*.es6 lib/presets/*.es6 test/",
11
11
  "pretest": "npm run lint && npm run compile",
12
12
  "test": ":",
13
- "posttest": "mocha --require babel-core/register --recursive --check-leaks --globals addresses",
13
+ "posttest": "mocha --require @babel/register --recursive --check-leaks --globals addresses",
14
14
  "prepare": "npm run compile",
15
15
  "release:patch": "release-it patch -n"
16
16
  },
@@ -28,29 +28,39 @@
28
28
  ],
29
29
  "babel": {
30
30
  "presets": [
31
- "env"
31
+ [
32
+ "@babel/env",
33
+ {
34
+ "targets": {
35
+ "node": 10
36
+ }
37
+ }
38
+ ]
32
39
  ]
33
40
  },
34
41
  "dependencies": {
35
42
  "cssnano": "^4.1.10",
36
- "normalize-html-whitespace": "^1.0.0",
37
- "object-assign": "^4.0.1",
38
- "posthtml": "^0.11.4",
39
- "posthtml-render": "^1.1.5",
40
- "svgo": "^1.2.2",
41
- "terser": "^4.1.2",
42
- "uncss": "^0.17.0"
43
+ "posthtml": "^0.13.4",
44
+ "posthtml-render": "^1.3.0",
45
+ "purgecss": "^2.3.0",
46
+ "relateurl": "^0.2.7",
47
+ "srcset": "^3.0.0",
48
+ "svgo": "^1.3.2",
49
+ "terser": "^4.8.0",
50
+ "timsort": "^0.3.0",
51
+ "uncss": "^0.17.3"
43
52
  },
44
53
  "devDependencies": {
45
- "babel-cli": "^6.26.0",
46
- "babel-core": "^6.26.3",
47
- "babel-eslint": "^10.0.2",
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"
54
+ "@babel/cli": "^7.12.1",
55
+ "@babel/core": "^7.12.3",
56
+ "@babel/preset-env": "^7.12.1",
57
+ "@babel/register": "^7.12.1",
58
+ "babel-eslint": "^10.1.0",
59
+ "eslint": "^7.13.0",
60
+ "expect": "^26.6.2",
61
+ "mocha": "^8.2.1",
62
+ "release-it": "^14.2.1",
63
+ "rimraf": "^3.0.2"
54
64
  },
55
65
  "repository": {
56
66
  "type": "git",