single-file-core 1.0.0
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/.eslintrc.js +43 -0
- package/LICENSE +661 -0
- package/index.js +86 -0
- package/modules/css-fonts-alt-minifier.js +340 -0
- package/modules/css-fonts-minifier.js +371 -0
- package/modules/css-matched-rules.js +350 -0
- package/modules/css-medias-alt-minifier.js +91 -0
- package/modules/css-rules-minifier.js +146 -0
- package/modules/html-images-alt-minifier.js +109 -0
- package/modules/html-minifier.js +262 -0
- package/modules/html-serializer.js +180 -0
- package/modules/index.js +42 -0
- package/package.json +18 -0
- package/processors/frame-tree/content/content-frame-tree.js +418 -0
- package/processors/hooks/content/content-hooks-frames-web.js +404 -0
- package/processors/hooks/content/content-hooks-frames.js +214 -0
- package/processors/hooks/content/content-hooks-web.js +48 -0
- package/processors/hooks/content/content-hooks.js +39 -0
- package/processors/index.js +34 -0
- package/processors/lazy/content/content-lazy-loader.js +229 -0
- package/single-file-bootstrap.js +33 -0
- package/single-file-core.js +2471 -0
- package/single-file-frames.js +1 -0
- package/single-file-helper.js +557 -0
- package/single-file-util.js +412 -0
- package/vendor/css-font-property-parser.js +343 -0
- package/vendor/css-media-query-parser.js +474 -0
- package/vendor/css-minifier.js +789 -0
- package/vendor/css-tree.js +35 -0
- package/vendor/css-unescape.js +72 -0
- package/vendor/html-srcset-parser.js +339 -0
- package/vendor/index.js +40 -0
- package/vendor/mime-type-parser.js +446 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2010-2020 Gildas Lormeau
|
|
3
|
+
* contact : gildas.lormeau <at> gmail.com
|
|
4
|
+
*
|
|
5
|
+
* This file is part of SingleFile.
|
|
6
|
+
*
|
|
7
|
+
* The code in this file is free software: you can redistribute it and/or
|
|
8
|
+
* modify it under the terms of the GNU Affero General Public License
|
|
9
|
+
* (GNU AGPL) as published by the Free Software Foundation, either version 3
|
|
10
|
+
* of the License, or (at your option) any later version.
|
|
11
|
+
*
|
|
12
|
+
* The code in this file is distributed in the hope that it will be useful,
|
|
13
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
|
|
15
|
+
* General Public License for more details.
|
|
16
|
+
*
|
|
17
|
+
* As additional permission under GNU AGPL version 3 section 7, you may
|
|
18
|
+
* distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
|
|
19
|
+
* AGPL normally required by section 4, provided you include this license
|
|
20
|
+
* notice and a URL through which recipients can access the Corresponding
|
|
21
|
+
* Source.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
// Derived from the work of Kirill Maltsev - https://github.com/posthtml/htmlnano
|
|
25
|
+
|
|
26
|
+
// Source: https://github.com/kangax/html-minifier/issues/63
|
|
27
|
+
const booleanAttributes = [
|
|
28
|
+
"allowfullscreen",
|
|
29
|
+
"async",
|
|
30
|
+
"autofocus",
|
|
31
|
+
"autoplay",
|
|
32
|
+
"checked",
|
|
33
|
+
"compact",
|
|
34
|
+
"controls",
|
|
35
|
+
"declare",
|
|
36
|
+
"default",
|
|
37
|
+
"defaultchecked",
|
|
38
|
+
"defaultmuted",
|
|
39
|
+
"defaultselected",
|
|
40
|
+
"defer",
|
|
41
|
+
"disabled",
|
|
42
|
+
"enabled",
|
|
43
|
+
"formnovalidate",
|
|
44
|
+
"hidden",
|
|
45
|
+
"indeterminate",
|
|
46
|
+
"inert",
|
|
47
|
+
"ismap",
|
|
48
|
+
"itemscope",
|
|
49
|
+
"loop",
|
|
50
|
+
"multiple",
|
|
51
|
+
"muted",
|
|
52
|
+
"nohref",
|
|
53
|
+
"noresize",
|
|
54
|
+
"noshade",
|
|
55
|
+
"novalidate",
|
|
56
|
+
"nowrap",
|
|
57
|
+
"open",
|
|
58
|
+
"pauseonexit",
|
|
59
|
+
"readonly",
|
|
60
|
+
"required",
|
|
61
|
+
"reversed",
|
|
62
|
+
"scoped",
|
|
63
|
+
"seamless",
|
|
64
|
+
"selected",
|
|
65
|
+
"sortable",
|
|
66
|
+
"truespeed",
|
|
67
|
+
"typemustmatch",
|
|
68
|
+
"visible"
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const noWhitespaceCollapseElements = ["script", "style", "pre", "textarea"];
|
|
72
|
+
|
|
73
|
+
// Source: https://www.w3.org/TR/html4/sgml/dtd.html#events (Generic Attributes)
|
|
74
|
+
const safeToRemoveAttrs = [
|
|
75
|
+
"id",
|
|
76
|
+
"class",
|
|
77
|
+
"style",
|
|
78
|
+
"lang",
|
|
79
|
+
"dir",
|
|
80
|
+
"onclick",
|
|
81
|
+
"ondblclick",
|
|
82
|
+
"onmousedown",
|
|
83
|
+
"onmouseup",
|
|
84
|
+
"onmouseover",
|
|
85
|
+
"onmousemove",
|
|
86
|
+
"onmouseout",
|
|
87
|
+
"onkeypress",
|
|
88
|
+
"onkeydown",
|
|
89
|
+
"onkeyup"
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
const redundantAttributes = {
|
|
93
|
+
"form": {
|
|
94
|
+
"method": "get"
|
|
95
|
+
},
|
|
96
|
+
"script": {
|
|
97
|
+
"language": "javascript",
|
|
98
|
+
"type": "text/javascript",
|
|
99
|
+
// Remove attribute if the function returns false
|
|
100
|
+
"charset": node => {
|
|
101
|
+
// The charset attribute only really makes sense on “external” SCRIPT elements:
|
|
102
|
+
// http://perfectionkills.com/optimizing-html/#8_script_charset
|
|
103
|
+
return !node.getAttribute("src");
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"style": {
|
|
107
|
+
"media": "all",
|
|
108
|
+
"type": "text/css"
|
|
109
|
+
},
|
|
110
|
+
"link": {
|
|
111
|
+
"media": "all"
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const REGEXP_WHITESPACE = /[ \t\f\r]+/g;
|
|
116
|
+
const REGEXP_NEWLINE = /[\n]+/g;
|
|
117
|
+
const REGEXP_ENDS_WHITESPACE = /^\s+$/;
|
|
118
|
+
const NodeFilter_SHOW_ALL = 4294967295;
|
|
119
|
+
const Node_ELEMENT_NODE = 1;
|
|
120
|
+
const Node_TEXT_NODE = 3;
|
|
121
|
+
const Node_COMMENT_NODE = 8;
|
|
122
|
+
|
|
123
|
+
const modules = [
|
|
124
|
+
collapseBooleanAttributes,
|
|
125
|
+
mergeTextNodes,
|
|
126
|
+
collapseWhitespace,
|
|
127
|
+
removeComments,
|
|
128
|
+
removeEmptyAttributes,
|
|
129
|
+
removeRedundantAttributes,
|
|
130
|
+
compressJSONLD,
|
|
131
|
+
node => mergeElements(node, "style", (node, previousSibling) => node.parentElement && node.parentElement.tagName == "HEAD" && node.media == previousSibling.media && node.title == previousSibling.title)
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
export {
|
|
135
|
+
process
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
function process(doc, options) {
|
|
139
|
+
removeEmptyInlineElements(doc);
|
|
140
|
+
const nodesWalker = doc.createTreeWalker(doc.documentElement, NodeFilter_SHOW_ALL, null, false);
|
|
141
|
+
let node = nodesWalker.nextNode();
|
|
142
|
+
while (node) {
|
|
143
|
+
const deletedNode = modules.find(module => module(node, options));
|
|
144
|
+
const previousNode = node;
|
|
145
|
+
node = nodesWalker.nextNode();
|
|
146
|
+
if (deletedNode) {
|
|
147
|
+
previousNode.remove();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function collapseBooleanAttributes(node) {
|
|
153
|
+
if (node.nodeType == Node_ELEMENT_NODE) {
|
|
154
|
+
Array.from(node.attributes).forEach(attribute => {
|
|
155
|
+
if (booleanAttributes.includes(attribute.name)) {
|
|
156
|
+
node.setAttribute(attribute.name, "");
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function mergeTextNodes(node) {
|
|
163
|
+
if (node.nodeType == Node_TEXT_NODE) {
|
|
164
|
+
if (node.previousSibling && node.previousSibling.nodeType == Node_TEXT_NODE) {
|
|
165
|
+
node.textContent = node.previousSibling.textContent + node.textContent;
|
|
166
|
+
node.previousSibling.remove();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function mergeElements(node, tagName, acceptMerge) {
|
|
172
|
+
if (node.nodeType == Node_ELEMENT_NODE && node.tagName.toLowerCase() == tagName.toLowerCase()) {
|
|
173
|
+
let previousSibling = node.previousSibling;
|
|
174
|
+
const previousSiblings = [];
|
|
175
|
+
while (previousSibling && previousSibling.nodeType == Node_TEXT_NODE && !previousSibling.textContent.trim()) {
|
|
176
|
+
previousSiblings.push(previousSibling);
|
|
177
|
+
previousSibling = previousSibling.previousSibling;
|
|
178
|
+
}
|
|
179
|
+
if (previousSibling && previousSibling.nodeType == Node_ELEMENT_NODE && previousSibling.tagName == node.tagName && acceptMerge(node, previousSibling)) {
|
|
180
|
+
node.textContent = previousSibling.textContent + node.textContent;
|
|
181
|
+
previousSiblings.forEach(node => node.remove());
|
|
182
|
+
previousSibling.remove();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function collapseWhitespace(node, options) {
|
|
188
|
+
if (node.nodeType == Node_TEXT_NODE) {
|
|
189
|
+
let element = node.parentElement;
|
|
190
|
+
const spacePreserved = element.getAttribute(options.PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME) == "";
|
|
191
|
+
if (!spacePreserved) {
|
|
192
|
+
const textContent = node.textContent;
|
|
193
|
+
let noWhitespace = noWhitespaceCollapse(element);
|
|
194
|
+
while (noWhitespace) {
|
|
195
|
+
element = element.parentElement;
|
|
196
|
+
noWhitespace = element && noWhitespaceCollapse(element);
|
|
197
|
+
}
|
|
198
|
+
if ((!element || noWhitespace) && textContent.length > 1) {
|
|
199
|
+
node.textContent = textContent.replace(REGEXP_WHITESPACE, getWhiteSpace(node)).replace(REGEXP_NEWLINE, "\n");
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function getWhiteSpace(node) {
|
|
206
|
+
return node.parentElement && node.parentElement.tagName == "HEAD" ? "\n" : " ";
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function noWhitespaceCollapse(element) {
|
|
210
|
+
return element && !noWhitespaceCollapseElements.includes(element.tagName.toLowerCase());
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function removeComments(node) {
|
|
214
|
+
if (node.nodeType == Node_COMMENT_NODE && node.parentElement.tagName != "HTML") {
|
|
215
|
+
return !node.textContent.toLowerCase().trim().startsWith("[if");
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function removeEmptyAttributes(node) {
|
|
220
|
+
if (node.nodeType == Node_ELEMENT_NODE) {
|
|
221
|
+
Array.from(node.attributes).forEach(attribute => {
|
|
222
|
+
if (safeToRemoveAttrs.includes(attribute.name.toLowerCase())) {
|
|
223
|
+
const attributeValue = node.getAttribute(attribute.name);
|
|
224
|
+
if (attributeValue == "" || (attributeValue || "").match(REGEXP_ENDS_WHITESPACE)) {
|
|
225
|
+
node.removeAttribute(attribute.name);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function removeRedundantAttributes(node) {
|
|
233
|
+
if (node.nodeType == Node_ELEMENT_NODE) {
|
|
234
|
+
const tagRedundantAttributes = redundantAttributes[node.tagName.toLowerCase()];
|
|
235
|
+
if (tagRedundantAttributes) {
|
|
236
|
+
Object.keys(tagRedundantAttributes).forEach(redundantAttributeName => {
|
|
237
|
+
const tagRedundantAttributeValue = tagRedundantAttributes[redundantAttributeName];
|
|
238
|
+
if (typeof tagRedundantAttributeValue == "function" ? tagRedundantAttributeValue(node) : node.getAttribute(redundantAttributeName) == tagRedundantAttributeValue) {
|
|
239
|
+
node.removeAttribute(redundantAttributeName);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function compressJSONLD(node) {
|
|
247
|
+
if (node.nodeType == Node_ELEMENT_NODE && node.tagName == "SCRIPT" && node.type == "application/ld+json" && node.textContent.trim()) {
|
|
248
|
+
try {
|
|
249
|
+
node.textContent = JSON.stringify(JSON.parse(node.textContent));
|
|
250
|
+
} catch (error) {
|
|
251
|
+
// ignored
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function removeEmptyInlineElements(doc) {
|
|
257
|
+
doc.querySelectorAll("style, script:not([src])").forEach(element => {
|
|
258
|
+
if (!element.textContent.trim()) {
|
|
259
|
+
element.remove();
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2010-2020 Gildas Lormeau
|
|
3
|
+
* contact : gildas.lormeau <at> gmail.com
|
|
4
|
+
*
|
|
5
|
+
* This file is part of SingleFile.
|
|
6
|
+
*
|
|
7
|
+
* The code in this file is free software: you can redistribute it and/or
|
|
8
|
+
* modify it under the terms of the GNU Affero General Public License
|
|
9
|
+
* (GNU AGPL) as published by the Free Software Foundation, either version 3
|
|
10
|
+
* of the License, or (at your option) any later version.
|
|
11
|
+
*
|
|
12
|
+
* The code in this file is distributed in the hope that it will be useful,
|
|
13
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
|
|
15
|
+
* General Public License for more details.
|
|
16
|
+
*
|
|
17
|
+
* As additional permission under GNU AGPL version 3 section 7, you may
|
|
18
|
+
* distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
|
|
19
|
+
* AGPL normally required by section 4, provided you include this license
|
|
20
|
+
* notice and a URL through which recipients can access the Corresponding
|
|
21
|
+
* Source.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const SELF_CLOSED_TAG_NAMES = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"];
|
|
25
|
+
|
|
26
|
+
const Node_ELEMENT_NODE = 1;
|
|
27
|
+
const Node_TEXT_NODE = 3;
|
|
28
|
+
const Node_COMMENT_NODE = 8;
|
|
29
|
+
|
|
30
|
+
// see https://www.w3.org/TR/html5/syntax.html#optional-tags
|
|
31
|
+
const OMITTED_START_TAGS = [
|
|
32
|
+
{ tagName: "head", accept: element => !element.childNodes.length || element.childNodes[0].nodeType == Node_ELEMENT_NODE },
|
|
33
|
+
{ tagName: "body", accept: element => !element.childNodes.length }
|
|
34
|
+
];
|
|
35
|
+
const OMITTED_END_TAGS = [
|
|
36
|
+
{ tagName: "html", accept: next => !next || next.nodeType != Node_COMMENT_NODE },
|
|
37
|
+
{ tagName: "head", accept: next => !next || (next.nodeType != Node_COMMENT_NODE && (next.nodeType != Node_TEXT_NODE || !startsWithSpaceChar(next.textContent))) },
|
|
38
|
+
{ tagName: "body", accept: next => !next || next.nodeType != Node_COMMENT_NODE },
|
|
39
|
+
{ tagName: "li", accept: (next, element) => (!next && element.parentElement && (element.parentElement.tagName == "UL" || element.parentElement.tagName == "OL")) || (next && ["LI"].includes(next.tagName)) },
|
|
40
|
+
{ tagName: "dt", accept: next => !next || ["DT", "DD"].includes(next.tagName) },
|
|
41
|
+
{ tagName: "p", accept: next => next && ["ADDRESS", "ARTICLE", "ASIDE", "BLOCKQUOTE", "DETAILS", "DIV", "DL", "FIELDSET", "FIGCAPTION", "FIGURE", "FOOTER", "FORM", "H1", "H2", "H3", "H4", "H5", "H6", "HEADER", "HR", "MAIN", "NAV", "OL", "P", "PRE", "SECTION", "TABLE", "UL"].includes(next.tagName) },
|
|
42
|
+
{ tagName: "dd", accept: next => !next || ["DT", "DD"].includes(next.tagName) },
|
|
43
|
+
{ tagName: "rt", accept: next => !next || ["RT", "RP"].includes(next.tagName) },
|
|
44
|
+
{ tagName: "rp", accept: next => !next || ["RT", "RP"].includes(next.tagName) },
|
|
45
|
+
{ tagName: "optgroup", accept: next => !next || ["OPTGROUP"].includes(next.tagName) },
|
|
46
|
+
{ tagName: "option", accept: next => !next || ["OPTION", "OPTGROUP"].includes(next.tagName) },
|
|
47
|
+
{ tagName: "colgroup", accept: next => !next || (next.nodeType != Node_COMMENT_NODE && (next.nodeType != Node_TEXT_NODE || !startsWithSpaceChar(next.textContent))) },
|
|
48
|
+
{ tagName: "caption", accept: next => !next || (next.nodeType != Node_COMMENT_NODE && (next.nodeType != Node_TEXT_NODE || !startsWithSpaceChar(next.textContent))) },
|
|
49
|
+
{ tagName: "thead", accept: next => !next || ["TBODY", "TFOOT"].includes(next.tagName) },
|
|
50
|
+
{ tagName: "tbody", accept: next => !next || ["TBODY", "TFOOT"].includes(next.tagName) },
|
|
51
|
+
{ tagName: "tfoot", accept: next => !next },
|
|
52
|
+
{ tagName: "tr", accept: next => !next || ["TR"].includes(next.tagName) },
|
|
53
|
+
{ tagName: "td", accept: next => !next || ["TD", "TH"].includes(next.tagName) },
|
|
54
|
+
{ tagName: "th", accept: next => !next || ["TD", "TH"].includes(next.tagName) }
|
|
55
|
+
];
|
|
56
|
+
const TEXT_NODE_TAGS = ["style", "script", "xmp", "iframe", "noembed", "noframes", "plaintext", "noscript"];
|
|
57
|
+
|
|
58
|
+
export {
|
|
59
|
+
process
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
function process(doc, compressHTML) {
|
|
63
|
+
const docType = doc.doctype;
|
|
64
|
+
let docTypeString = "";
|
|
65
|
+
if (docType) {
|
|
66
|
+
docTypeString = "<!DOCTYPE " + docType.nodeName;
|
|
67
|
+
if (docType.publicId) {
|
|
68
|
+
docTypeString += " PUBLIC \"" + docType.publicId + "\"";
|
|
69
|
+
if (docType.systemId)
|
|
70
|
+
docTypeString += " \"" + docType.systemId + "\"";
|
|
71
|
+
} else if (docType.systemId)
|
|
72
|
+
docTypeString += " SYSTEM \"" + docType.systemId + "\"";
|
|
73
|
+
if (docType.internalSubset)
|
|
74
|
+
docTypeString += " [" + docType.internalSubset + "]";
|
|
75
|
+
docTypeString += "> ";
|
|
76
|
+
}
|
|
77
|
+
return docTypeString + serialize(doc.documentElement, compressHTML);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function serialize(node, compressHTML, isSVG) {
|
|
81
|
+
if (node.nodeType == Node_TEXT_NODE) {
|
|
82
|
+
return serializeTextNode(node);
|
|
83
|
+
} else if (node.nodeType == Node_COMMENT_NODE) {
|
|
84
|
+
return serializeCommentNode(node);
|
|
85
|
+
} else if (node.nodeType == Node_ELEMENT_NODE) {
|
|
86
|
+
return serializeElement(node, compressHTML, isSVG);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function serializeTextNode(textNode) {
|
|
91
|
+
const parentNode = textNode.parentNode;
|
|
92
|
+
let parentTagName;
|
|
93
|
+
if (parentNode && parentNode.nodeType == Node_ELEMENT_NODE) {
|
|
94
|
+
parentTagName = parentNode.tagName.toLowerCase();
|
|
95
|
+
}
|
|
96
|
+
if (!parentTagName || TEXT_NODE_TAGS.includes(parentTagName)) {
|
|
97
|
+
if (parentTagName == "script") {
|
|
98
|
+
return textNode.textContent.replace(/<\//gi, "<\\/").replace(/\/>/gi, "\\/>");
|
|
99
|
+
}
|
|
100
|
+
return textNode.textContent;
|
|
101
|
+
} else {
|
|
102
|
+
return textNode.textContent.replace(/&/g, "&").replace(/\u00a0/g, " ").replace(/</g, "<").replace(/>/g, ">");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function serializeCommentNode(commentNode) {
|
|
107
|
+
return "<!--" + commentNode.textContent + "-->";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function serializeElement(element, compressHTML, isSVG) {
|
|
111
|
+
const tagName = element.tagName.toLowerCase();
|
|
112
|
+
const omittedStartTag = compressHTML && OMITTED_START_TAGS.find(omittedStartTag => tagName == omittedStartTag.tagName && omittedStartTag.accept(element));
|
|
113
|
+
let content = "";
|
|
114
|
+
if (!omittedStartTag || element.attributes.length) {
|
|
115
|
+
content = "<" + tagName;
|
|
116
|
+
Array.from(element.attributes).forEach(attribute => content += serializeAttribute(attribute, element, compressHTML));
|
|
117
|
+
content += ">";
|
|
118
|
+
}
|
|
119
|
+
if (element.tagName == "TEMPLATE" && !element.childNodes.length) {
|
|
120
|
+
content += element.innerHTML;
|
|
121
|
+
} else {
|
|
122
|
+
Array.from(element.childNodes).forEach(childNode => content += serialize(childNode, compressHTML, isSVG || tagName == "svg"));
|
|
123
|
+
}
|
|
124
|
+
const omittedEndTag = compressHTML && OMITTED_END_TAGS.find(omittedEndTag => tagName == omittedEndTag.tagName && omittedEndTag.accept(element.nextSibling, element));
|
|
125
|
+
if (isSVG || (!omittedEndTag && !SELF_CLOSED_TAG_NAMES.includes(tagName))) {
|
|
126
|
+
content += "</" + tagName + ">";
|
|
127
|
+
}
|
|
128
|
+
return content;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function serializeAttribute(attribute, element, compressHTML) {
|
|
132
|
+
const name = attribute.name;
|
|
133
|
+
let content = "";
|
|
134
|
+
if (!name.match(/["'>/=]/)) {
|
|
135
|
+
let value = attribute.value;
|
|
136
|
+
if (compressHTML && name == "class") {
|
|
137
|
+
value = Array.from(element.classList).map(className => className.trim()).join(" ");
|
|
138
|
+
}
|
|
139
|
+
let simpleQuotesValue;
|
|
140
|
+
value = value.replace(/&/g, "&").replace(/\u00a0/g, " ");
|
|
141
|
+
if (value.includes("\"")) {
|
|
142
|
+
if (value.includes("'") || !compressHTML) {
|
|
143
|
+
value = value.replace(/"/g, """);
|
|
144
|
+
} else {
|
|
145
|
+
simpleQuotesValue = true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const invalidUnquotedValue = !compressHTML || !value.match(/^[^ \t\n\f\r'"`=<>]+$/);
|
|
149
|
+
content += " ";
|
|
150
|
+
if (!attribute.namespace) {
|
|
151
|
+
content += name;
|
|
152
|
+
} else if (attribute.namespaceURI == "http://www.w3.org/XML/1998/namespace") {
|
|
153
|
+
content += "xml:" + name;
|
|
154
|
+
} else if (attribute.namespaceURI == "http://www.w3.org/2000/xmlns/") {
|
|
155
|
+
if (name !== "xmlns") {
|
|
156
|
+
content += "xmlns:";
|
|
157
|
+
}
|
|
158
|
+
content += name;
|
|
159
|
+
} else if (attribute.namespaceURI == "http://www.w3.org/1999/xlink") {
|
|
160
|
+
content += "xlink:" + name;
|
|
161
|
+
} else {
|
|
162
|
+
content += name;
|
|
163
|
+
}
|
|
164
|
+
if (value != "") {
|
|
165
|
+
content += "=";
|
|
166
|
+
if (invalidUnquotedValue) {
|
|
167
|
+
content += simpleQuotesValue ? "'" : "\"";
|
|
168
|
+
}
|
|
169
|
+
content += value;
|
|
170
|
+
if (invalidUnquotedValue) {
|
|
171
|
+
content += simpleQuotesValue ? "'" : "\"";
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return content;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function startsWithSpaceChar(textContent) {
|
|
179
|
+
return Boolean(textContent.match(/^[ \t\n\f\r]/));
|
|
180
|
+
}
|
package/modules/index.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2010-2020 Gildas Lormeau
|
|
3
|
+
* contact : gildas.lormeau <at> gmail.com
|
|
4
|
+
*
|
|
5
|
+
* This file is part of SingleFile.
|
|
6
|
+
*
|
|
7
|
+
* The code in this file is free software: you can redistribute it and/or
|
|
8
|
+
* modify it under the terms of the GNU Affero General Public License
|
|
9
|
+
* (GNU AGPL) as published by the Free Software Foundation, either version 3
|
|
10
|
+
* of the License, or (at your option) any later version.
|
|
11
|
+
*
|
|
12
|
+
* The code in this file is distributed in the hope that it will be useful,
|
|
13
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
|
|
15
|
+
* General Public License for more details.
|
|
16
|
+
*
|
|
17
|
+
* As additional permission under GNU AGPL version 3 section 7, you may
|
|
18
|
+
* distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
|
|
19
|
+
* AGPL normally required by section 4, provided you include this license
|
|
20
|
+
* notice and a URL through which recipients can access the Corresponding
|
|
21
|
+
* Source.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import * as fontsAltMinifier from "./css-fonts-alt-minifier.js";
|
|
25
|
+
import * as fontsMinifier from "./css-fonts-minifier";
|
|
26
|
+
import * as matchedRules from "./css-matched-rules.js";
|
|
27
|
+
import * as mediasAltMinifier from "./css-medias-alt-minifier.js";
|
|
28
|
+
import * as cssRulesMinifier from "./css-rules-minifier.js";
|
|
29
|
+
import * as imagesAltMinifier from "./html-images-alt-minifier.js";
|
|
30
|
+
import * as htmlMinifier from "./html-minifier.js";
|
|
31
|
+
import * as serializer from "./html-serializer.js";
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
fontsAltMinifier,
|
|
35
|
+
fontsMinifier,
|
|
36
|
+
matchedRules,
|
|
37
|
+
mediasAltMinifier,
|
|
38
|
+
cssRulesMinifier,
|
|
39
|
+
imagesAltMinifier,
|
|
40
|
+
htmlMinifier,
|
|
41
|
+
serializer
|
|
42
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "single-file-core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "SingleFile Core",
|
|
5
|
+
"author": "Gildas Lormeau",
|
|
6
|
+
"license": "AGPL-3.0-or-later",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/gildas-lormeau/single-file-core.git"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/gildas-lormeau/single-file-core/issues"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/gildas-lormeau/single-file-core#readme"
|
|
18
|
+
}
|