flow like the river
This commit is contained in:
commit
013fe673f3
42435 changed files with 5764238 additions and 0 deletions
41
BACK_BACK/node_modules/htmlnano/lib/helpers.js
generated
vendored
Executable file
41
BACK_BACK/node_modules/htmlnano/lib/helpers.js
generated
vendored
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.isAmpBoilerplate = isAmpBoilerplate;
|
||||
exports.isComment = isComment;
|
||||
exports.isConditionalComment = isConditionalComment;
|
||||
exports.isStyleNode = isStyleNode;
|
||||
exports.extractCssFromStyleNode = extractCssFromStyleNode;
|
||||
const ampBoilerplateAttributes = ['amp-boilerplate', 'amp4ads-boilerplate', 'amp4email-boilerplate'];
|
||||
|
||||
function isAmpBoilerplate(node) {
|
||||
if (!node.attrs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const attr of ampBoilerplateAttributes) {
|
||||
if (attr in node.attrs) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function isComment(content) {
|
||||
return (content || '').trim().startsWith('<!--');
|
||||
}
|
||||
|
||||
function isConditionalComment(content) {
|
||||
return (content || '').trim().startsWith('<!--[if');
|
||||
}
|
||||
|
||||
function isStyleNode(node) {
|
||||
return node.tag === 'style' && !isAmpBoilerplate(node) && 'content' in node && node.content.length > 0;
|
||||
}
|
||||
|
||||
function extractCssFromStyleNode(node) {
|
||||
return Array.isArray(node.content) ? node.content.join(' ') : node.content;
|
||||
}
|
||||
54
BACK_BACK/node_modules/htmlnano/lib/htmlnano.js
generated
vendored
Executable file
54
BACK_BACK/node_modules/htmlnano/lib/htmlnano.js
generated
vendored
Executable file
|
|
@ -0,0 +1,54 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
|
||||
var _posthtml = _interopRequireDefault(require("posthtml"));
|
||||
|
||||
var _safe = _interopRequireDefault(require("./presets/safe"));
|
||||
|
||||
var _ampSafe = _interopRequireDefault(require("./presets/ampSafe"));
|
||||
|
||||
var _max = _interopRequireDefault(require("./presets/max"));
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
function htmlnano(options = {}, preset = _safe.default) {
|
||||
return function minifier(tree) {
|
||||
options = { ...preset,
|
||||
...options
|
||||
};
|
||||
let promise = Promise.resolve(tree);
|
||||
|
||||
for (const [moduleName, moduleOptions] of Object.entries(options)) {
|
||||
if (!moduleOptions) {
|
||||
// The module is disabled
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_safe.default[moduleName] === undefined) {
|
||||
throw new Error('Module "' + moduleName + '" is not defined');
|
||||
}
|
||||
|
||||
let module = require('./modules/' + moduleName);
|
||||
|
||||
promise = promise.then(tree => module.default(tree, options, moduleOptions));
|
||||
}
|
||||
|
||||
return promise;
|
||||
};
|
||||
}
|
||||
|
||||
htmlnano.process = function (html, options, preset, postHtmlOptions) {
|
||||
return (0, _posthtml.default)([htmlnano(options, preset)]).process(html, postHtmlOptions);
|
||||
};
|
||||
|
||||
htmlnano.presets = {
|
||||
safe: _safe.default,
|
||||
ampSafe: _ampSafe.default,
|
||||
max: _max.default
|
||||
};
|
||||
var _default = htmlnano;
|
||||
exports.default = _default;
|
||||
32
BACK_BACK/node_modules/htmlnano/lib/modules/collapseAttributeWhitespace.js
generated
vendored
Executable file
32
BACK_BACK/node_modules/htmlnano/lib/modules/collapseAttributeWhitespace.js
generated
vendored
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = collapseAttributeWhitespace;
|
||||
exports.attributesWithLists = void 0;
|
||||
const attributesWithLists = new Set(['class', 'rel', 'ping']);
|
||||
/** Collapse whitespaces inside list-like attributes (e.g. class, rel) */
|
||||
|
||||
exports.attributesWithLists = attributesWithLists;
|
||||
|
||||
function collapseAttributeWhitespace(tree) {
|
||||
tree.walk(node => {
|
||||
if (!node.attrs) {
|
||||
return node;
|
||||
}
|
||||
|
||||
Object.entries(node.attrs).forEach(([attrName, attrValue]) => {
|
||||
const attrNameLower = attrName.toLowerCase();
|
||||
|
||||
if (!attributesWithLists.has(attrNameLower)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newAttrValue = attrValue.replace(/\s+/g, ' ').trim();
|
||||
node.attrs[attrName] = newAttrValue;
|
||||
});
|
||||
return node;
|
||||
});
|
||||
return tree;
|
||||
}
|
||||
44
BACK_BACK/node_modules/htmlnano/lib/modules/collapseBooleanAttributes.js
generated
vendored
Executable file
44
BACK_BACK/node_modules/htmlnano/lib/modules/collapseBooleanAttributes.js
generated
vendored
Executable file
|
|
@ -0,0 +1,44 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = collapseBooleanAttributes;
|
||||
// Source: https://github.com/kangax/html-minifier/issues/63
|
||||
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']);
|
||||
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']);
|
||||
|
||||
function collapseBooleanAttributes(tree, options, moduleOptions) {
|
||||
tree.walk(node => {
|
||||
if (!node.attrs) {
|
||||
return node;
|
||||
}
|
||||
|
||||
if (!node.tag) {
|
||||
return node;
|
||||
}
|
||||
|
||||
for (const attrName of Object.keys(node.attrs)) {
|
||||
if (attrName === 'visible' && node.tag.startsWith('a-')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (htmlBooleanAttributes.has(attrName)) {
|
||||
node.attrs[attrName] = true;
|
||||
}
|
||||
|
||||
if (moduleOptions.amphtml && amphtmlBooleanAttributes.has(attrName) && node.attrs[attrName] === '') {
|
||||
node.attrs[attrName] = true;
|
||||
} // collapse crossorigin attributes
|
||||
// Specification: https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cors-settings-attributes
|
||||
|
||||
|
||||
if (attrName.toLowerCase() === 'crossorigin' && (node.attrs[attrName] === 'anonymous' || node.attrs[attrName] === '')) {
|
||||
node.attrs[attrName] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
return tree;
|
||||
}
|
||||
76
BACK_BACK/node_modules/htmlnano/lib/modules/collapseWhitespace.js
generated
vendored
Executable file
76
BACK_BACK/node_modules/htmlnano/lib/modules/collapseWhitespace.js
generated
vendored
Executable file
|
|
@ -0,0 +1,76 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = collapseWhitespace;
|
||||
|
||||
var _helpers = require("../helpers");
|
||||
|
||||
const noWhitespaceCollapseElements = new Set(['script', 'style', 'pre', 'textarea']);
|
||||
const noTrimWhitespacesArroundElements = new Set([// non-empty tags that will maintain whitespace around them
|
||||
'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
|
||||
'comment', 'img', 'input', 'wbr']);
|
||||
const noTrimWhitespacesInsideElements = new Set([// non-empty tags that will maintain whitespace within them
|
||||
'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']);
|
||||
const whitespacePattern = /\s+/g;
|
||||
const NONE = '';
|
||||
const SINGLE_SPACE = ' ';
|
||||
const validOptions = ['all', 'aggressive', 'conservative'];
|
||||
/** Collapses redundant whitespaces */
|
||||
|
||||
function collapseWhitespace(tree, options, collapseType, tag) {
|
||||
collapseType = validOptions.includes(collapseType) ? collapseType : 'conservative';
|
||||
tree.forEach((node, index) => {
|
||||
if (typeof node === 'string') {
|
||||
const prevNode = tree[index - 1];
|
||||
const nextNode = tree[index + 1];
|
||||
const prevNodeTag = prevNode && prevNode.tag;
|
||||
const nextNodeTag = nextNode && nextNode.tag;
|
||||
const isTopLevel = !tag || tag === 'html' || tag === 'head';
|
||||
const shouldTrim = collapseType === 'all' || isTopLevel ||
|
||||
/*
|
||||
* When collapseType is set to 'aggressive', and the tag is not inside 'noTrimWhitespacesInsideElements'.
|
||||
* the first & last space inside the tag will be trimmed
|
||||
*/
|
||||
collapseType === 'aggressive' && !noTrimWhitespacesInsideElements.has(tag);
|
||||
node = collapseRedundantWhitespaces(node, collapseType, shouldTrim, tag, prevNodeTag, nextNodeTag);
|
||||
}
|
||||
|
||||
const isAllowCollapseWhitespace = !noWhitespaceCollapseElements.has(node.tag);
|
||||
|
||||
if (node.content && node.content.length && isAllowCollapseWhitespace) {
|
||||
node.content = collapseWhitespace(node.content, options, collapseType, node.tag);
|
||||
}
|
||||
|
||||
tree[index] = node;
|
||||
});
|
||||
return tree;
|
||||
}
|
||||
|
||||
function collapseRedundantWhitespaces(text, collapseType, shouldTrim = false, currentTag, prevNodeTag, nextNodeTag) {
|
||||
if (!text || text.length === 0) {
|
||||
return NONE;
|
||||
}
|
||||
|
||||
if (!(0, _helpers.isComment)(text)) {
|
||||
text = text.replace(whitespacePattern, SINGLE_SPACE);
|
||||
}
|
||||
|
||||
if (shouldTrim) {
|
||||
if (collapseType === 'aggressive') {
|
||||
if (!noTrimWhitespacesArroundElements.has(prevNodeTag)) {
|
||||
text = text.trimStart();
|
||||
}
|
||||
|
||||
if (!noTrimWhitespacesArroundElements.has(nextNodeTag)) {
|
||||
text = text.trimEnd();
|
||||
}
|
||||
} else {
|
||||
// collapseType is 'all', trim spaces
|
||||
text = text.trim();
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
22
BACK_BACK/node_modules/htmlnano/lib/modules/custom.js
generated
vendored
Executable file
22
BACK_BACK/node_modules/htmlnano/lib/modules/custom.js
generated
vendored
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = custom;
|
||||
|
||||
/** Meta-module that runs custom modules */
|
||||
function custom(tree, options, customModules) {
|
||||
if (!customModules) {
|
||||
return tree;
|
||||
}
|
||||
|
||||
if (!Array.isArray(customModules)) {
|
||||
customModules = [customModules];
|
||||
}
|
||||
|
||||
customModules.forEach(customModule => {
|
||||
tree = customModule(tree, options);
|
||||
});
|
||||
return tree;
|
||||
}
|
||||
46
BACK_BACK/node_modules/htmlnano/lib/modules/deduplicateAttributeValues.js
generated
vendored
Executable file
46
BACK_BACK/node_modules/htmlnano/lib/modules/deduplicateAttributeValues.js
generated
vendored
Executable file
|
|
@ -0,0 +1,46 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = collapseAttributeWhitespace;
|
||||
|
||||
var _collapseAttributeWhitespace = require("./collapseAttributeWhitespace");
|
||||
|
||||
/** Deduplicate values inside list-like attributes (e.g. class, rel) */
|
||||
function collapseAttributeWhitespace(tree) {
|
||||
tree.walk(node => {
|
||||
if (!node.attrs) {
|
||||
return node;
|
||||
}
|
||||
|
||||
Object.keys(node.attrs).forEach(attrName => {
|
||||
const attrNameLower = attrName.toLowerCase();
|
||||
|
||||
if (!_collapseAttributeWhitespace.attributesWithLists.has(attrNameLower)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const attrValues = node.attrs[attrName].split(/\s/);
|
||||
const uniqeAttrValues = new Set();
|
||||
const deduplicatedAttrValues = [];
|
||||
attrValues.forEach(attrValue => {
|
||||
if (!attrValue) {
|
||||
// Keep whitespaces
|
||||
deduplicatedAttrValues.push('');
|
||||
return;
|
||||
}
|
||||
|
||||
if (uniqeAttrValues.has(attrValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
deduplicatedAttrValues.push(attrValue);
|
||||
uniqeAttrValues.add(attrValue);
|
||||
});
|
||||
node.attrs[attrName] = deduplicatedAttrValues.join(' ');
|
||||
});
|
||||
return node;
|
||||
});
|
||||
return tree;
|
||||
}
|
||||
63
BACK_BACK/node_modules/htmlnano/lib/modules/mergeScripts.js
generated
vendored
Executable file
63
BACK_BACK/node_modules/htmlnano/lib/modules/mergeScripts.js
generated
vendored
Executable file
|
|
@ -0,0 +1,63 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = mergeScripts;
|
||||
|
||||
/* Merge multiple <script> into one */
|
||||
function mergeScripts(tree) {
|
||||
let scriptNodesIndex = {};
|
||||
let scriptSrcIndex = 1;
|
||||
tree.match({
|
||||
tag: 'script'
|
||||
}, node => {
|
||||
const nodeAttrs = node.attrs || {};
|
||||
|
||||
if (nodeAttrs.src) {
|
||||
scriptSrcIndex++;
|
||||
return node;
|
||||
}
|
||||
|
||||
const scriptType = nodeAttrs.type || 'text/javascript';
|
||||
|
||||
if (scriptType !== 'text/javascript' && scriptType !== 'application/javascript') {
|
||||
return node;
|
||||
}
|
||||
|
||||
const scriptKey = JSON.stringify({
|
||||
id: nodeAttrs.id,
|
||||
class: nodeAttrs.class,
|
||||
type: scriptType,
|
||||
defer: nodeAttrs.defer !== undefined,
|
||||
async: nodeAttrs.async !== undefined,
|
||||
index: scriptSrcIndex
|
||||
});
|
||||
|
||||
if (!scriptNodesIndex[scriptKey]) {
|
||||
scriptNodesIndex[scriptKey] = [];
|
||||
}
|
||||
|
||||
scriptNodesIndex[scriptKey].push(node);
|
||||
return node;
|
||||
});
|
||||
|
||||
for (const scriptNodes of Object.values(scriptNodesIndex)) {
|
||||
let lastScriptNode = scriptNodes.pop();
|
||||
scriptNodes.reverse().forEach(scriptNode => {
|
||||
let scriptContent = (scriptNode.content || []).join(' ');
|
||||
scriptContent = scriptContent.trim();
|
||||
|
||||
if (scriptContent.slice(-1) !== ';') {
|
||||
scriptContent += ';';
|
||||
}
|
||||
|
||||
lastScriptNode.content = lastScriptNode.content || [];
|
||||
lastScriptNode.content.unshift(scriptContent);
|
||||
scriptNode.tag = false;
|
||||
scriptNode.content = [];
|
||||
});
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
42
BACK_BACK/node_modules/htmlnano/lib/modules/mergeStyles.js
generated
vendored
Executable file
42
BACK_BACK/node_modules/htmlnano/lib/modules/mergeStyles.js
generated
vendored
Executable file
|
|
@ -0,0 +1,42 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = mergeStyles;
|
||||
|
||||
var _helpers = require("../helpers");
|
||||
|
||||
/* Merge multiple <style> into one */
|
||||
function mergeStyles(tree) {
|
||||
const styleNodes = {};
|
||||
tree.match({
|
||||
tag: 'style'
|
||||
}, node => {
|
||||
const nodeAttrs = node.attrs || {}; // Skip <style scoped></style>
|
||||
// https://developer.mozilla.org/en/docs/Web/HTML/Element/style
|
||||
|
||||
if (nodeAttrs.scoped !== undefined) {
|
||||
return node;
|
||||
}
|
||||
|
||||
if ((0, _helpers.isAmpBoilerplate)(node)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
const styleType = nodeAttrs.type || 'text/css';
|
||||
const styleMedia = nodeAttrs.media || 'all';
|
||||
const styleKey = styleType + '_' + styleMedia;
|
||||
|
||||
if (styleNodes[styleKey]) {
|
||||
const styleContent = (node.content || []).join(' ');
|
||||
styleNodes[styleKey].content.push(' ' + styleContent);
|
||||
return '';
|
||||
}
|
||||
|
||||
node.content = node.content || [];
|
||||
styleNodes[styleKey] = node;
|
||||
return node;
|
||||
});
|
||||
return tree;
|
||||
}
|
||||
58
BACK_BACK/node_modules/htmlnano/lib/modules/minifyConditionalComments.js
generated
vendored
Executable file
58
BACK_BACK/node_modules/htmlnano/lib/modules/minifyConditionalComments.js
generated
vendored
Executable file
|
|
@ -0,0 +1,58 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = minifyConditionalComments;
|
||||
|
||||
var _htmlnano = _interopRequireDefault(require("../htmlnano"));
|
||||
|
||||
var _helpers = require("../helpers");
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
// Spec: https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/ms537512(v=vs.85)
|
||||
const CONDITIONAL_COMMENT_REGEXP = /(<!--\[if\s+?[^<>[\]]+?]>)([\s\S]+?)(<!\[endif\]-->)/gm;
|
||||
/** Minify content inside conditional comments */
|
||||
|
||||
async function minifyConditionalComments(tree, htmlnanoOptions) {
|
||||
// forEach, tree.walk, tree.match just don't support Promise.
|
||||
for (let i = 0, len = tree.length; i < len; i++) {
|
||||
const node = tree[i];
|
||||
|
||||
if (typeof node === 'string' && (0, _helpers.isConditionalComment)(node)) {
|
||||
tree[i] = await minifycontentInsideConditionalComments(node, htmlnanoOptions);
|
||||
}
|
||||
|
||||
if (node.content && node.content.length) {
|
||||
tree[i].content = await minifyConditionalComments(node.content, htmlnanoOptions);
|
||||
}
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
async function minifycontentInsideConditionalComments(text, htmlnanoOptions) {
|
||||
let match;
|
||||
const matches = []; // FIXME!
|
||||
// String#matchAll is supported since Node.js 12
|
||||
|
||||
while ((match = CONDITIONAL_COMMENT_REGEXP.exec(text)) !== null) {
|
||||
matches.push([match[1], match[2], match[3]]);
|
||||
}
|
||||
|
||||
if (!matches.length) {
|
||||
return Promise.resolve(text);
|
||||
}
|
||||
|
||||
return Promise.all(matches.map(async match => {
|
||||
const result = await _htmlnano.default.process(match[1], htmlnanoOptions, {}, {});
|
||||
let minified = result.html;
|
||||
|
||||
if (match[1].includes('<html') && minified.includes('</html>')) {
|
||||
minified = minified.replace('</html>', '');
|
||||
}
|
||||
|
||||
return match[0] + minified + match[2];
|
||||
}));
|
||||
}
|
||||
78
BACK_BACK/node_modules/htmlnano/lib/modules/minifyCss.js
generated
vendored
Executable file
78
BACK_BACK/node_modules/htmlnano/lib/modules/minifyCss.js
generated
vendored
Executable file
|
|
@ -0,0 +1,78 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = minifyCss;
|
||||
|
||||
var _helpers = require("../helpers");
|
||||
|
||||
var _cssnano = _interopRequireDefault(require("cssnano"));
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
const postcssOptions = {
|
||||
// Prevent the following warning from being shown:
|
||||
// > Without `from` option PostCSS could generate wrong source map and will not find Browserslist config.
|
||||
// > Set it to CSS file path or to `undefined` to prevent this warning.
|
||||
from: undefined
|
||||
};
|
||||
/** Minify CSS with cssnano */
|
||||
|
||||
function minifyCss(tree, options, cssnanoOptions) {
|
||||
let promises = [];
|
||||
tree.walk(node => {
|
||||
if ((0, _helpers.isStyleNode)(node)) {
|
||||
promises.push(processStyleNode(node, cssnanoOptions));
|
||||
} else if (node.attrs && node.attrs.style) {
|
||||
promises.push(processStyleAttr(node, cssnanoOptions));
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
return Promise.all(promises).then(() => tree);
|
||||
}
|
||||
|
||||
function processStyleNode(styleNode, cssnanoOptions) {
|
||||
let css = (0, _helpers.extractCssFromStyleNode)(styleNode); // Improve performance by avoiding calling stripCdata again and again
|
||||
|
||||
let isCdataWrapped = false;
|
||||
|
||||
if (css.includes('CDATA')) {
|
||||
const strippedCss = stripCdata(css);
|
||||
isCdataWrapped = css !== strippedCss;
|
||||
css = strippedCss;
|
||||
}
|
||||
|
||||
return _cssnano.default.process(css, postcssOptions, cssnanoOptions).then(result => {
|
||||
if (isCdataWrapped) {
|
||||
return styleNode.content = ['<![CDATA[' + result + ']]>'];
|
||||
}
|
||||
|
||||
return styleNode.content = [result.css];
|
||||
});
|
||||
}
|
||||
|
||||
function processStyleAttr(node, cssnanoOptions) {
|
||||
// CSS "color: red;" is invalid. Therefore it should be wrapped inside some selector:
|
||||
// a{color: red;}
|
||||
const wrapperStart = 'a{';
|
||||
const wrapperEnd = '}';
|
||||
const wrappedStyle = wrapperStart + (node.attrs.style || '') + wrapperEnd;
|
||||
return _cssnano.default.process(wrappedStyle, postcssOptions, cssnanoOptions).then(result => {
|
||||
const minifiedCss = result.css; // Remove wrapperStart at the start and wrapperEnd at the end of minifiedCss
|
||||
|
||||
node.attrs.style = minifiedCss.substring(wrapperStart.length, minifiedCss.length - wrapperEnd.length);
|
||||
});
|
||||
}
|
||||
|
||||
function stripCdata(css) {
|
||||
const leftStrippedCss = css.replace('<![CDATA[', '');
|
||||
|
||||
if (leftStrippedCss === css) {
|
||||
return css;
|
||||
}
|
||||
|
||||
const strippedCss = leftStrippedCss.replace(']]>', '');
|
||||
return leftStrippedCss === strippedCss ? css : strippedCss;
|
||||
}
|
||||
109
BACK_BACK/node_modules/htmlnano/lib/modules/minifyJs.js
generated
vendored
Executable file
109
BACK_BACK/node_modules/htmlnano/lib/modules/minifyJs.js
generated
vendored
Executable file
|
|
@ -0,0 +1,109 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = minifyJs;
|
||||
|
||||
var _terser = _interopRequireDefault(require("terser"));
|
||||
|
||||
var _removeRedundantAttributes = require("./removeRedundantAttributes");
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
/** Minify JS with Terser */
|
||||
function minifyJs(tree, options, terserOptions) {
|
||||
let promises = [];
|
||||
tree.walk(node => {
|
||||
if (node.tag && node.tag === 'script') {
|
||||
const nodeAttrs = node.attrs || {};
|
||||
const mimeType = nodeAttrs.type || 'text/javascript';
|
||||
|
||||
if (_removeRedundantAttributes.redundantScriptTypes.has(mimeType) || mimeType === 'module') {
|
||||
promises.push(processScriptNode(node, terserOptions));
|
||||
}
|
||||
}
|
||||
|
||||
if (node.attrs) {
|
||||
promises = promises.concat(processNodeWithOnAttrs(node, terserOptions));
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
return Promise.all(promises).then(() => tree);
|
||||
}
|
||||
|
||||
function stripCdata(js) {
|
||||
const leftStrippedJs = js.replace(/\/\/\s*<!\[CDATA\[/, '').replace(/\/\*\s*<!\[CDATA\[\s*\*\//, '');
|
||||
|
||||
if (leftStrippedJs === js) {
|
||||
return js;
|
||||
}
|
||||
|
||||
const strippedJs = leftStrippedJs.replace(/\/\/\s*\]\]>/, '').replace(/\/\*\s*\]\]>\s*\*\//, '');
|
||||
return leftStrippedJs === strippedJs ? js : strippedJs;
|
||||
}
|
||||
|
||||
function processScriptNode(scriptNode, terserOptions) {
|
||||
let js = (scriptNode.content || []).join('').trim();
|
||||
|
||||
if (!js) {
|
||||
return scriptNode;
|
||||
} // Improve performance by avoiding calling stripCdata again and again
|
||||
|
||||
|
||||
let isCdataWrapped = false;
|
||||
|
||||
if (js.includes('CDATA')) {
|
||||
const strippedJs = stripCdata(js);
|
||||
isCdataWrapped = js !== strippedJs;
|
||||
js = strippedJs;
|
||||
}
|
||||
|
||||
return _terser.default.minify(js, terserOptions).then(result => {
|
||||
if (result.error) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
if (result.code === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let content = result.code;
|
||||
|
||||
if (isCdataWrapped) {
|
||||
content = '/*<![CDATA[*/' + content + '/*]]>*/';
|
||||
}
|
||||
|
||||
scriptNode.content = [content];
|
||||
});
|
||||
}
|
||||
|
||||
function processNodeWithOnAttrs(node, terserOptions) {
|
||||
const jsWrapperStart = 'function _(){';
|
||||
const jsWrapperEnd = '}';
|
||||
const promises = [];
|
||||
|
||||
for (const attrName of Object.keys(node.attrs || {})) {
|
||||
if (!attrName.startsWith('on')) {
|
||||
continue;
|
||||
} // For example onclick="return false" is valid,
|
||||
// but "return false;" is invalid (error: 'return' outside of function)
|
||||
// Therefore the attribute's code should be wrapped inside function:
|
||||
// "function _(){return false;}"
|
||||
|
||||
|
||||
let wrappedJs = jsWrapperStart + node.attrs[attrName] + jsWrapperEnd;
|
||||
|
||||
let promise = _terser.default.minify(wrappedJs, terserOptions).then(({
|
||||
code
|
||||
}) => {
|
||||
let minifiedJs = code.substring(jsWrapperStart.length, code.length - jsWrapperEnd.length);
|
||||
node.attrs[attrName] = minifiedJs;
|
||||
});
|
||||
|
||||
promises.push(promise);
|
||||
}
|
||||
|
||||
return promises;
|
||||
}
|
||||
33
BACK_BACK/node_modules/htmlnano/lib/modules/minifyJson.js
generated
vendored
Executable file
33
BACK_BACK/node_modules/htmlnano/lib/modules/minifyJson.js
generated
vendored
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = minifyJson;
|
||||
|
||||
/* Minify JSON inside <script> tags */
|
||||
function minifyJson(tree) {
|
||||
// Match all <script> tags which have JSON mime type
|
||||
tree.match({
|
||||
tag: 'script',
|
||||
attrs: {
|
||||
type: /(\/|\+)json/
|
||||
}
|
||||
}, node => {
|
||||
let content = (node.content || []).join('');
|
||||
|
||||
if (!content) {
|
||||
return node;
|
||||
}
|
||||
|
||||
try {
|
||||
content = JSON.stringify(JSON.parse(content));
|
||||
} catch (error) {
|
||||
return node;
|
||||
}
|
||||
|
||||
node.content = [content];
|
||||
return node;
|
||||
});
|
||||
return tree;
|
||||
}
|
||||
32
BACK_BACK/node_modules/htmlnano/lib/modules/minifySvg.js
generated
vendored
Executable file
32
BACK_BACK/node_modules/htmlnano/lib/modules/minifySvg.js
generated
vendored
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = minifySvg;
|
||||
|
||||
var _svgo = _interopRequireDefault(require("svgo"));
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
/** Minify SVG with SVGO */
|
||||
function minifySvg(tree, options, svgoOptions = {}) {
|
||||
let promises = [];
|
||||
const svgo = new _svgo.default(svgoOptions);
|
||||
tree.match({
|
||||
tag: 'svg'
|
||||
}, node => {
|
||||
let svgStr = tree.render(node, {
|
||||
closingSingleTag: 'slash',
|
||||
quoteAllAttributes: true
|
||||
});
|
||||
let promise = svgo.optimize(svgStr).then(result => {
|
||||
node.tag = false;
|
||||
node.attrs = {};
|
||||
node.content = result.data;
|
||||
});
|
||||
promises.push(promise);
|
||||
return node;
|
||||
});
|
||||
return Promise.all(promises).then(() => tree);
|
||||
}
|
||||
116
BACK_BACK/node_modules/htmlnano/lib/modules/minifyUrls.js
generated
vendored
Executable file
116
BACK_BACK/node_modules/htmlnano/lib/modules/minifyUrls.js
generated
vendored
Executable file
|
|
@ -0,0 +1,116 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = minifyUrls;
|
||||
|
||||
var _relateurl = _interopRequireDefault(require("relateurl"));
|
||||
|
||||
var _srcset = _interopRequireDefault(require("srcset"));
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
// Adopts from https://github.com/kangax/html-minifier/blob/51ce10f4daedb1de483ffbcccecc41be1c873da2/src/htmlminifier.js#L209-L221
|
||||
const tagsHaveUriValuesForAttributes = new Set(['a', 'area', 'link', 'base', 'img', 'object', 'q', 'blockquote', 'ins', 'form', 'input', 'head', 'script']);
|
||||
const tagsHasHrefAttributes = new Set(['a', 'area', 'link', 'base']);
|
||||
const attributesOfImgTagHasUriValues = new Set(['src', 'longdesc', 'usemap']);
|
||||
const attributesOfObjectTagHasUriValues = new Set(['classid', 'codebase', 'data', 'usemap']);
|
||||
|
||||
const isUriTypeAttribute = (tag, attr) => {
|
||||
return tagsHasHrefAttributes.has(tag) && attr === 'href' || tag === 'img' && attributesOfImgTagHasUriValues.has(attr) || tag === 'object' && attributesOfObjectTagHasUriValues.has(attr) || tag === 'q' && attr === 'cite' || tag === 'blockquote' && attr === 'cite' || (tag === 'ins' || tag === 'del') && attr === 'cite' || tag === 'form' && attr === 'action' || tag === 'input' && (attr === 'src' || attr === 'usemap') || tag === 'head' && attr === 'profile' || tag === 'script' && (attr === 'src' || attr === 'for') ||
|
||||
/**
|
||||
* https://html.spec.whatwg.org/#attr-source-src
|
||||
*
|
||||
* Although most of browsers recommend not to use "src" in <source>,
|
||||
* but technically it does comply with HTML Standard.
|
||||
*/
|
||||
tag === 'source' && attr === 'src';
|
||||
};
|
||||
|
||||
const isSrcsetAttribute = (tag, attr) => {
|
||||
return tag === 'source' && attr === 'srcset' || tag === 'img' && attr === 'srcset' || tag === 'link' && attr === 'imagesrcset';
|
||||
};
|
||||
|
||||
const processModuleOptions = options => {
|
||||
// FIXME!
|
||||
// relateurl@1.0.0-alpha only supports URL while stable version (0.2.7) only supports string
|
||||
// should convert input into URL instance after relateurl@1 is stable
|
||||
if (typeof options === 'string') return options;
|
||||
if (options instanceof URL) return options.toString();
|
||||
return false;
|
||||
};
|
||||
|
||||
const isLinkRelCanonical = ({
|
||||
tag,
|
||||
attrs
|
||||
}) => {
|
||||
// Return false early for non-"link" tag
|
||||
if (tag !== 'link') return false;
|
||||
|
||||
for (const [attrName, attrValue] of Object.entries(attrs)) {
|
||||
if (attrName.toLowerCase() === 'rel' && attrValue === 'canonical') return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
let relateUrlInstance;
|
||||
let STORED_URL_BASE;
|
||||
/** Convert absolute url into relative url */
|
||||
|
||||
function minifyUrls(tree, options, moduleOptions) {
|
||||
const urlBase = processModuleOptions(moduleOptions); // Invalid configuration, return tree directly
|
||||
|
||||
if (!urlBase) return tree;
|
||||
/** Bring up a reusable RelateUrl instances (only once)
|
||||
*
|
||||
* STORED_URL_BASE is used to invalidate RelateUrl instances,
|
||||
* avoiding require.cache acrossing multiple htmlnano instance with different configuration,
|
||||
* e.g. unit tests cases.
|
||||
*/
|
||||
|
||||
if (!relateUrlInstance || STORED_URL_BASE !== urlBase) {
|
||||
relateUrlInstance = new _relateurl.default(urlBase);
|
||||
STORED_URL_BASE = urlBase;
|
||||
}
|
||||
|
||||
tree.walk(node => {
|
||||
if (!node.attrs) return node;
|
||||
if (!node.tag) return node;
|
||||
if (!tagsHaveUriValuesForAttributes.has(node.tag)) return node; // Prevent link[rel=canonical] being processed
|
||||
// Can't be excluded by isUriTypeAttribute()
|
||||
|
||||
if (isLinkRelCanonical(node)) return node;
|
||||
|
||||
for (const [attrName, attrValue] of Object.entries(node.attrs)) {
|
||||
const attrNameLower = attrName.toLowerCase();
|
||||
|
||||
if (isUriTypeAttribute(node.tag, attrNameLower)) {
|
||||
// FIXME!
|
||||
// relateurl@1.0.0-alpha only supports URL while stable version (0.2.7) only supports string
|
||||
// the WHATWG URL API is very strict while attrValue might not be a valid URL
|
||||
// new URL should be used, and relateUrl#relate should be wrapped in try...catch after relateurl@1 is stable
|
||||
node.attrs[attrName] = relateUrlInstance.relate(attrValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isSrcsetAttribute(node.tag, attrNameLower)) {
|
||||
try {
|
||||
const parsedSrcset = _srcset.default.parse(attrValue);
|
||||
|
||||
node.attrs[attrName] = _srcset.default.stringify(parsedSrcset.map(srcset => {
|
||||
srcset.url = relateUrlInstance.relate(srcset.url);
|
||||
return srcset;
|
||||
}));
|
||||
} catch (e) {// srcset will throw an Error for invalid srcset.
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
return tree;
|
||||
}
|
||||
19
BACK_BACK/node_modules/htmlnano/lib/modules/removeAttributeQuotes.js
generated
vendored
Executable file
19
BACK_BACK/node_modules/htmlnano/lib/modules/removeAttributeQuotes.js
generated
vendored
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = removeAttributeQuotes;
|
||||
|
||||
// Specification: https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
|
||||
// See also: https://github.com/posthtml/posthtml-render/pull/30
|
||||
// See also: https://github.com/posthtml/htmlnano/issues/6#issuecomment-707105334
|
||||
|
||||
/** Disable quoteAllAttributes while not overriding the configuration */
|
||||
function removeAttributeQuotes(tree) {
|
||||
if (tree.options && typeof tree.options.quoteAllAttributes === 'undefined') {
|
||||
tree.options.quoteAllAttributes = false;
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
72
BACK_BACK/node_modules/htmlnano/lib/modules/removeComments.js
generated
vendored
Executable file
72
BACK_BACK/node_modules/htmlnano/lib/modules/removeComments.js
generated
vendored
Executable file
|
|
@ -0,0 +1,72 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = removeComments;
|
||||
|
||||
var _helpers = require("../helpers");
|
||||
|
||||
const MATCH_EXCERPT_REGEXP = /<!-- ?more ?-->/i;
|
||||
/** Removes HTML comments */
|
||||
|
||||
function removeComments(tree, options, removeType) {
|
||||
if (removeType !== 'all' && removeType !== 'safe') {
|
||||
removeType = 'safe';
|
||||
}
|
||||
|
||||
tree.walk(node => {
|
||||
if (node.contents && node.contents.length) {
|
||||
node.contents = node.contents.filter(content => !isCommentToRemove(content, removeType));
|
||||
} else if (isCommentToRemove(node, removeType)) {
|
||||
node = '';
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
return tree;
|
||||
}
|
||||
|
||||
function isCommentToRemove(text, removeType) {
|
||||
if (typeof text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(0, _helpers.isComment)(text)) {
|
||||
// Not HTML comment
|
||||
return false;
|
||||
}
|
||||
|
||||
if (removeType === 'safe') {
|
||||
const isNoindex = text === '<!--noindex-->' || text === '<!--/noindex-->'; // Don't remove noindex comments.
|
||||
// See: https://yandex.com/support/webmaster/controlling-robot/html.xml
|
||||
|
||||
if (isNoindex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isServerSideExclude = text === '<!--sse-->' || text === '<!--/sse-->'; // Don't remove sse comments.
|
||||
// See: https://support.cloudflare.com/hc/en-us/articles/200170036-What-does-Server-Side-Excludes-SSE-do-
|
||||
|
||||
if (isServerSideExclude) {
|
||||
return false;
|
||||
} // https://en.wikipedia.org/wiki/Conditional_comment
|
||||
|
||||
|
||||
if ((0, _helpers.isConditionalComment)(text)) {
|
||||
return false;
|
||||
} // Hexo: https://hexo.io/docs/tag-plugins#Post-Excerpt
|
||||
// Hugo: https://gohugo.io/content-management/summaries/#manual-summary-splitting
|
||||
// WordPress: https://wordpress.com/support/wordpress-editor/blocks/more-block/2/
|
||||
// Jekyll: https://jekyllrb.com/docs/posts/#post-excerpts
|
||||
|
||||
|
||||
const isCMSExcerptComment = MATCH_EXCERPT_REGEXP.test(text);
|
||||
|
||||
if (isCMSExcerptComment) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
31
BACK_BACK/node_modules/htmlnano/lib/modules/removeEmptyAttributes.js
generated
vendored
Executable file
31
BACK_BACK/node_modules/htmlnano/lib/modules/removeEmptyAttributes.js
generated
vendored
Executable file
|
|
@ -0,0 +1,31 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = removeEmptyAttributes;
|
||||
// Source: https://www.w3.org/TR/html4/sgml/dtd.html#events (Generic Attributes)
|
||||
const safeToRemoveAttrs = new Set(['id', 'class', 'style', 'title', 'lang', 'dir', 'onclick', 'ondblclick', 'onmousedown', 'onmouseup', 'onmouseover', 'onmousemove', 'onmouseout', 'onkeypress', 'onkeydown', 'onkeyup']);
|
||||
/** Removes empty attributes */
|
||||
|
||||
function removeEmptyAttributes(tree) {
|
||||
tree.walk(node => {
|
||||
if (!node.attrs) {
|
||||
return node;
|
||||
}
|
||||
|
||||
Object.entries(node.attrs).forEach(([attrName, attrValue]) => {
|
||||
const attrNameLower = attrName.toLowerCase();
|
||||
|
||||
if (!safeToRemoveAttrs.has(attrNameLower)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (attrValue === '' || (attrValue || '').match(/^\s+$/)) {
|
||||
delete node.attrs[attrName];
|
||||
}
|
||||
});
|
||||
return node;
|
||||
});
|
||||
return tree;
|
||||
}
|
||||
220
BACK_BACK/node_modules/htmlnano/lib/modules/removeOptionalTags.js
generated
vendored
Executable file
220
BACK_BACK/node_modules/htmlnano/lib/modules/removeOptionalTags.js
generated
vendored
Executable file
|
|
@ -0,0 +1,220 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = removeOptionalTags;
|
||||
|
||||
var _helpers = require("../helpers");
|
||||
|
||||
const startWithWhitespacePattern = /^\s+/;
|
||||
const bodyStartTagCantBeOmittedWithFirstChildTags = new Set(['meta', 'link', 'script', 'style']);
|
||||
const tbodyStartTagCantBeOmittedWithPrecededTags = new Set(['tbody', 'thead', 'tfoot']);
|
||||
const tbodyEndTagCantBeOmittedWithFollowedTags = new Set(['tbody', 'tfoot']);
|
||||
|
||||
function isEmptyTextNode(node) {
|
||||
if (typeof node === 'string' && node.trim() === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function isEmptyNode(node) {
|
||||
if (!node.content) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (node.content.length) {
|
||||
return !node.content.filter(n => typeof n === 'string' && isEmptyTextNode(n) ? false : true).length;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function getFirstChildTag(node, nonEmpty = true) {
|
||||
if (node.content && node.content.length) {
|
||||
if (nonEmpty) {
|
||||
for (const childNode of node.content) {
|
||||
if (childNode.tag) return childNode;
|
||||
if (typeof childNode === 'string' && !isEmptyTextNode(childNode)) return childNode;
|
||||
}
|
||||
} else {
|
||||
return node.content[0] || null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getPrevNode(tree, currentNodeIndex, nonEmpty = false) {
|
||||
if (nonEmpty) {
|
||||
for (let i = currentNodeIndex - 1; i >= 0; i--) {
|
||||
const node = tree[i];
|
||||
if (node.tag) return node;
|
||||
if (typeof node === 'string' && !isEmptyTextNode(node)) return node;
|
||||
}
|
||||
} else {
|
||||
return tree[currentNodeIndex - 1] || null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getNextNode(tree, currentNodeIndex, nonEmpty = false) {
|
||||
if (nonEmpty) {
|
||||
for (let i = currentNodeIndex + 1; i < tree.length; i++) {
|
||||
const node = tree[i];
|
||||
if (node.tag) return node;
|
||||
if (typeof node === 'string' && !isEmptyTextNode(node)) return node;
|
||||
}
|
||||
} else {
|
||||
return tree[currentNodeIndex + 1] || null;
|
||||
}
|
||||
|
||||
return null;
|
||||
} // Specification https://html.spec.whatwg.org/multipage/syntax.html#optional-tags
|
||||
|
||||
/** Remove optional tag in the DOM */
|
||||
|
||||
|
||||
function removeOptionalTags(tree) {
|
||||
tree.forEach((node, index) => {
|
||||
if (!node.tag) return node;
|
||||
if (node.attrs && Object.keys(node.attrs).length) return node; // const prevNode = getPrevNode(tree, index);
|
||||
|
||||
const prevNonEmptyNode = getPrevNode(tree, index, true);
|
||||
const nextNode = getNextNode(tree, index);
|
||||
const nextNonEmptyNode = getNextNode(tree, index, true);
|
||||
const firstChildNode = getFirstChildTag(node, false);
|
||||
const firstNonEmptyChildNode = getFirstChildTag(node);
|
||||
/**
|
||||
* An "html" element's start tag may be omitted if the first thing inside the "html" element is not a comment.
|
||||
* An "html" element's end tag may be omitted if the "html" element is not IMMEDIATELY followed by a comment.
|
||||
*/
|
||||
|
||||
if (node.tag === 'html') {
|
||||
let isHtmlStartTagCanBeOmitted = true;
|
||||
let isHtmlEndTagCanBeOmitted = true;
|
||||
|
||||
if (typeof firstNonEmptyChildNode === 'string' && (0, _helpers.isComment)(firstNonEmptyChildNode)) {
|
||||
isHtmlStartTagCanBeOmitted = false;
|
||||
}
|
||||
|
||||
if (typeof nextNonEmptyNode === 'string' && (0, _helpers.isComment)(nextNonEmptyNode)) {
|
||||
isHtmlEndTagCanBeOmitted = false;
|
||||
}
|
||||
|
||||
if (isHtmlStartTagCanBeOmitted && isHtmlEndTagCanBeOmitted) {
|
||||
node.tag = false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* A "head" element's start tag may be omitted if the element is empty, or if the first thing inside the "head" element is an element.
|
||||
* A "head" element's end tag may be omitted if the "head" element is not IMMEDIATELY followed by ASCII whitespace or a comment.
|
||||
*/
|
||||
|
||||
|
||||
if (node.tag === 'head') {
|
||||
let isHeadStartTagCanBeOmitted = false;
|
||||
let isHeadEndTagCanBeOmitted = true;
|
||||
|
||||
if (isEmptyNode(node) || firstNonEmptyChildNode && firstNonEmptyChildNode.tag) {
|
||||
isHeadStartTagCanBeOmitted = true;
|
||||
}
|
||||
|
||||
if (nextNode && typeof nextNode === 'string' && startWithWhitespacePattern.test(nextNode) || nextNonEmptyNode && typeof nextNonEmptyNode === 'string' && (0, _helpers.isComment)(nextNode)) {
|
||||
isHeadEndTagCanBeOmitted = false;
|
||||
}
|
||||
|
||||
if (isHeadStartTagCanBeOmitted && isHeadEndTagCanBeOmitted) {
|
||||
node.tag = false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* A "body" element's start tag may be omitted if the element is empty, or if the first thing inside the "body" element is not ASCII whitespace or a comment, except if the first thing inside the "body" element is a "meta", "link", "script", "style", or "template" element.
|
||||
* A "body" element's end tag may be omitted if the "body" element is not IMMEDIATELY followed by a comment.
|
||||
*/
|
||||
|
||||
|
||||
if (node.tag === 'body') {
|
||||
let isBodyStartTagCanBeOmitted = true;
|
||||
let isBodyEndTagCanBeOmitted = true;
|
||||
|
||||
if (typeof firstChildNode === 'string' && startWithWhitespacePattern.test(firstChildNode) || typeof firstNonEmptyChildNode === 'string' && (0, _helpers.isComment)(firstNonEmptyChildNode)) {
|
||||
isBodyStartTagCanBeOmitted = false;
|
||||
}
|
||||
|
||||
if (firstNonEmptyChildNode && firstNonEmptyChildNode.tag && bodyStartTagCantBeOmittedWithFirstChildTags.has(firstNonEmptyChildNode.tag)) {
|
||||
isBodyStartTagCanBeOmitted = false;
|
||||
}
|
||||
|
||||
if (nextNode && typeof nextNode === 'string' && (0, _helpers.isComment)(nextNode)) {
|
||||
isBodyEndTagCanBeOmitted = false;
|
||||
}
|
||||
|
||||
if (isBodyStartTagCanBeOmitted && isBodyEndTagCanBeOmitted) {
|
||||
node.tag = false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* A "colgroup" element's start tag may be omitted if the first thing inside the "colgroup" element is a "col" element, and if the element is not IMMEDIATELY preceded by another "colgroup" element. It can't be omitted if the element is empty.
|
||||
* A "colgroup" element's end tag may be omitted if the "colgroup" element is not IMMEDIATELY followed by ASCII whitespace or a comment.
|
||||
*/
|
||||
|
||||
|
||||
if (node.tag === 'colgroup') {
|
||||
let isColgroupStartTagCanBeOmitted = false;
|
||||
let isColgroupEndTagCanBeOmitted = true;
|
||||
|
||||
if (firstNonEmptyChildNode && firstNonEmptyChildNode.tag && firstNonEmptyChildNode.tag === 'col') {
|
||||
isColgroupStartTagCanBeOmitted = true;
|
||||
}
|
||||
|
||||
if (prevNonEmptyNode && prevNonEmptyNode.tag && prevNonEmptyNode.tag === 'colgroup') {
|
||||
isColgroupStartTagCanBeOmitted = false;
|
||||
}
|
||||
|
||||
if (nextNode && typeof nextNode === 'string' && startWithWhitespacePattern.test(nextNode) || nextNonEmptyNode && typeof nextNonEmptyNode === 'string' && (0, _helpers.isComment)(nextNonEmptyNode)) {
|
||||
isColgroupEndTagCanBeOmitted = false;
|
||||
}
|
||||
|
||||
if (isColgroupStartTagCanBeOmitted && isColgroupEndTagCanBeOmitted) {
|
||||
node.tag = false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* A "tbody" element's start tag may be omitted if the first thing inside the "tbody" element is a "tr" element, and if the element is not immediately preceded by another "tbody", "thead" or "tfoot" element. It can't be omitted if the element is empty.
|
||||
* A "tbody" element's end tag may be omitted if the "tbody" element is not IMMEDIATELY followed by a "tbody" or "tfoot" element.
|
||||
*/
|
||||
|
||||
|
||||
if (node.tag === 'tbody') {
|
||||
let isTbodyStartTagCanBeOmitted = false;
|
||||
let isTbodyEndTagCanBeOmitted = true;
|
||||
|
||||
if (firstNonEmptyChildNode && firstNonEmptyChildNode.tag && firstNonEmptyChildNode.tag === 'tr') {
|
||||
isTbodyStartTagCanBeOmitted = true;
|
||||
}
|
||||
|
||||
if (prevNonEmptyNode && prevNonEmptyNode.tag && tbodyStartTagCantBeOmittedWithPrecededTags.has(prevNonEmptyNode.tag)) {
|
||||
isTbodyStartTagCanBeOmitted = false;
|
||||
}
|
||||
|
||||
if (nextNonEmptyNode && nextNonEmptyNode.tag && tbodyEndTagCantBeOmittedWithFollowedTags.has(nextNonEmptyNode.tag)) {
|
||||
isTbodyEndTagCanBeOmitted = false;
|
||||
}
|
||||
|
||||
if (isTbodyStartTagCanBeOmitted && isTbodyEndTagCanBeOmitted) {
|
||||
node.tag = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (node.content && node.content.length) {
|
||||
removeOptionalTags(node.content);
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
return tree;
|
||||
}
|
||||
110
BACK_BACK/node_modules/htmlnano/lib/modules/removeRedundantAttributes.js
generated
vendored
Executable file
110
BACK_BACK/node_modules/htmlnano/lib/modules/removeRedundantAttributes.js
generated
vendored
Executable file
|
|
@ -0,0 +1,110 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = removeRedundantAttributes;
|
||||
exports.redundantScriptTypes = void 0;
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#JavaScript_types
|
||||
const redundantScriptTypes = new Set(['application/javascript', 'application/ecmascript', 'application/x-ecmascript', 'application/x-javascript', 'text/javascript', 'text/ecmascript', 'text/javascript1.0', 'text/javascript1.1', 'text/javascript1.2', 'text/javascript1.3', 'text/javascript1.4', 'text/javascript1.5', 'text/jscript', 'text/livescript', 'text/x-ecmascript', 'text/x-javascript']);
|
||||
exports.redundantScriptTypes = redundantScriptTypes;
|
||||
const redundantAttributes = {
|
||||
'form': {
|
||||
'method': 'get'
|
||||
},
|
||||
'input': {
|
||||
'type': 'text'
|
||||
},
|
||||
'button': {
|
||||
'type': 'submit'
|
||||
},
|
||||
'script': {
|
||||
'language': 'javascript',
|
||||
'type': node => {
|
||||
for (const [attrName, attrValue] of Object.entries(node.attrs)) {
|
||||
if (attrName.toLowerCase() !== 'type') {
|
||||
continue;
|
||||
}
|
||||
|
||||
return redundantScriptTypes.has(attrValue);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
// Remove attribute if the function returns false
|
||||
'charset': node => {
|
||||
// The charset attribute only really makes sense on “external” SCRIPT elements:
|
||||
// http://perfectionkills.com/optimizing-html/#8_script_charset
|
||||
return node.attrs && !node.attrs.src;
|
||||
}
|
||||
},
|
||||
'style': {
|
||||
'media': 'all',
|
||||
'type': 'text/css'
|
||||
},
|
||||
'link': {
|
||||
'media': 'all',
|
||||
'type': node => {
|
||||
// https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet
|
||||
let isRelStyleSheet = false;
|
||||
let isTypeTextCSS = false;
|
||||
|
||||
if (node.attrs) {
|
||||
for (const [attrName, attrValue] of Object.entries(node.attrs)) {
|
||||
if (attrName.toLowerCase() === 'rel' && attrValue === 'stylesheet') {
|
||||
isRelStyleSheet = true;
|
||||
}
|
||||
|
||||
if (attrName.toLowerCase() === 'type' && attrValue === 'text/css') {
|
||||
isTypeTextCSS = true;
|
||||
}
|
||||
}
|
||||
} // Only "text/css" is redudant for link[rel=stylesheet]. Otherwise "type" shouldn't be removed
|
||||
|
||||
|
||||
return isRelStyleSheet && isTypeTextCSS;
|
||||
}
|
||||
},
|
||||
// See: https://html.spec.whatwg.org/#lazy-loading-attributes
|
||||
'img': {
|
||||
'loading': 'eager'
|
||||
},
|
||||
'iframe': {
|
||||
'loading': 'eager'
|
||||
}
|
||||
};
|
||||
const tagsHaveRedundantAttributes = new Set(Object.keys(redundantAttributes));
|
||||
/** Removes redundant attributes */
|
||||
|
||||
function removeRedundantAttributes(tree) {
|
||||
tree.walk(node => {
|
||||
if (!node.tag) {
|
||||
return node;
|
||||
}
|
||||
|
||||
if (!tagsHaveRedundantAttributes.has(node.tag)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
const tagRedundantAttributes = redundantAttributes[node.tag];
|
||||
node.attrs = node.attrs || {};
|
||||
|
||||
for (const redundantAttributeName of Object.keys(tagRedundantAttributes)) {
|
||||
let tagRedundantAttributeValue = tagRedundantAttributes[redundantAttributeName];
|
||||
let isRemove = false;
|
||||
|
||||
if (typeof tagRedundantAttributeValue === 'function') {
|
||||
isRemove = tagRedundantAttributeValue(node);
|
||||
} else if (node.attrs[redundantAttributeName] === tagRedundantAttributeValue) {
|
||||
isRemove = true;
|
||||
}
|
||||
|
||||
if (isRemove) {
|
||||
delete node.attrs[redundantAttributeName];
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
return tree;
|
||||
}
|
||||
125
BACK_BACK/node_modules/htmlnano/lib/modules/removeUnusedCss.js
generated
vendored
Executable file
125
BACK_BACK/node_modules/htmlnano/lib/modules/removeUnusedCss.js
generated
vendored
Executable file
|
|
@ -0,0 +1,125 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = removeUnusedCss;
|
||||
|
||||
var _helpers = require("../helpers");
|
||||
|
||||
var _uncss = _interopRequireDefault(require("uncss"));
|
||||
|
||||
var _purgecss = _interopRequireDefault(require("purgecss"));
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
// These options must be set and shouldn't be overriden to ensure uncss doesn't look at linked stylesheets.
|
||||
const uncssOptions = {
|
||||
ignoreSheets: [/\s*/],
|
||||
stylesheets: []
|
||||
};
|
||||
|
||||
function processStyleNodeUnCSS(html, styleNode, uncssOptions) {
|
||||
const css = (0, _helpers.extractCssFromStyleNode)(styleNode);
|
||||
return runUncss(html, css, uncssOptions).then(css => {
|
||||
// uncss may have left some style tags empty
|
||||
if (css.trim().length === 0) {
|
||||
styleNode.tag = false;
|
||||
styleNode.content = [];
|
||||
return;
|
||||
}
|
||||
|
||||
styleNode.content = [css];
|
||||
});
|
||||
}
|
||||
|
||||
function runUncss(html, css, userOptions) {
|
||||
if (typeof userOptions !== 'object') {
|
||||
userOptions = {};
|
||||
}
|
||||
|
||||
const options = { ...userOptions,
|
||||
...uncssOptions
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
options.raw = css;
|
||||
(0, _uncss.default)(html, options, (error, output) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(output);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const purgeFromHtml = function (tree) {
|
||||
// content is not used as we can directly used the parsed HTML,
|
||||
// making the process faster
|
||||
const selectors = [];
|
||||
tree.walk(node => {
|
||||
const classes = node.attrs && node.attrs.class && node.attrs.class.split(' ') || [];
|
||||
const ids = node.attrs && node.attrs.id && node.attrs.id.split(' ') || [];
|
||||
selectors.push(...classes, ...ids);
|
||||
node.tag && selectors.push(node.tag);
|
||||
return node;
|
||||
});
|
||||
return () => selectors;
|
||||
};
|
||||
|
||||
function processStyleNodePurgeCSS(tree, styleNode, purgecssOptions) {
|
||||
const css = (0, _helpers.extractCssFromStyleNode)(styleNode);
|
||||
return runPurgecss(tree, css, purgecssOptions).then(css => {
|
||||
if (css.trim().length === 0) {
|
||||
styleNode.tag = false;
|
||||
styleNode.content = [];
|
||||
return;
|
||||
}
|
||||
|
||||
styleNode.content = [css];
|
||||
});
|
||||
}
|
||||
|
||||
function runPurgecss(tree, css, userOptions) {
|
||||
if (typeof userOptions !== 'object') {
|
||||
userOptions = {};
|
||||
}
|
||||
|
||||
const options = { ...userOptions,
|
||||
content: [{
|
||||
raw: tree,
|
||||
extension: 'html'
|
||||
}],
|
||||
css: [{
|
||||
raw: css,
|
||||
extension: 'css'
|
||||
}],
|
||||
extractors: [{
|
||||
extractor: purgeFromHtml(tree),
|
||||
extensions: ['html']
|
||||
}]
|
||||
};
|
||||
return new _purgecss.default().purge(options).then(result => {
|
||||
return result[0].css;
|
||||
});
|
||||
}
|
||||
/** Remove unused CSS */
|
||||
|
||||
|
||||
function removeUnusedCss(tree, options, userOptions) {
|
||||
const promises = [];
|
||||
const html = userOptions.tool !== 'purgeCSS' && tree.render(tree);
|
||||
tree.walk(node => {
|
||||
if ((0, _helpers.isStyleNode)(node)) {
|
||||
if (userOptions.tool === 'purgeCSS') {
|
||||
promises.push(processStyleNodePurgeCSS(tree, node, userOptions));
|
||||
} else {
|
||||
promises.push(processStyleNodeUnCSS(html, node, userOptions));
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
return Promise.all(promises).then(() => tree);
|
||||
}
|
||||
115
BACK_BACK/node_modules/htmlnano/lib/modules/sortAttributes.js
generated
vendored
Executable file
115
BACK_BACK/node_modules/htmlnano/lib/modules/sortAttributes.js
generated
vendored
Executable file
|
|
@ -0,0 +1,115 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = sortAttributes;
|
||||
|
||||
var _timsort = require("timsort");
|
||||
|
||||
const validOptions = new Set(['frequency', 'alphabetical']);
|
||||
|
||||
const processModuleOptions = options => {
|
||||
if (options === true) return 'alphabetical';
|
||||
return validOptions.has(options) ? options : false;
|
||||
};
|
||||
|
||||
class AttributeTokenChain {
|
||||
constructor() {
|
||||
this.freqData = new Map(); // <attr, frequency>[]
|
||||
}
|
||||
|
||||
addFromNodeAttrs(nodeAttrs) {
|
||||
Object.keys(nodeAttrs).forEach(attrName => {
|
||||
const attrNameLower = attrName.toLowerCase();
|
||||
|
||||
if (this.freqData.has(attrNameLower)) {
|
||||
this.freqData.set(attrNameLower, this.freqData.get(attrNameLower) + 1);
|
||||
} else {
|
||||
this.freqData.set(attrNameLower, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createSortOrder() {
|
||||
let _sortOrder = [...this.freqData.entries()];
|
||||
(0, _timsort.sort)(_sortOrder, (a, b) => b[1] - a[1]);
|
||||
this.sortOrder = _sortOrder.map(i => i[0]);
|
||||
}
|
||||
|
||||
sortFromNodeAttrs(nodeAttrs) {
|
||||
const newAttrs = {}; // Convert node.attrs attrName into lower case.
|
||||
|
||||
const loweredNodeAttrs = {};
|
||||
Object.entries(nodeAttrs).forEach(([attrName, attrValue]) => {
|
||||
loweredNodeAttrs[attrName.toLowerCase()] = attrValue;
|
||||
});
|
||||
|
||||
if (!this.sortOrder) {
|
||||
this.createSortOrder();
|
||||
}
|
||||
|
||||
this.sortOrder.forEach(attrNameLower => {
|
||||
// The attrName inside "sortOrder" has been lowered
|
||||
if (loweredNodeAttrs[attrNameLower]) {
|
||||
newAttrs[attrNameLower] = loweredNodeAttrs[attrNameLower];
|
||||
}
|
||||
});
|
||||
return newAttrs;
|
||||
}
|
||||
|
||||
}
|
||||
/** Sort attibutes */
|
||||
|
||||
|
||||
function sortAttributes(tree, options, moduleOptions) {
|
||||
const sortType = processModuleOptions(moduleOptions);
|
||||
|
||||
if (sortType === 'alphabetical') {
|
||||
return sortAttributesInAlphabeticalOrder(tree);
|
||||
}
|
||||
|
||||
if (sortType === 'frequency') {
|
||||
return sortAttributesByFrequency(tree);
|
||||
} // Invalid configuration
|
||||
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
function sortAttributesInAlphabeticalOrder(tree) {
|
||||
tree.walk(node => {
|
||||
if (!node.attrs) {
|
||||
return node;
|
||||
}
|
||||
|
||||
const newAttrs = {};
|
||||
Object.keys(node.attrs).sort((a, b) => typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b).forEach(attr => newAttrs[attr] = node.attrs[attr]);
|
||||
node.attrs = newAttrs;
|
||||
return node;
|
||||
});
|
||||
return tree;
|
||||
}
|
||||
|
||||
function sortAttributesByFrequency(tree) {
|
||||
const tokenchain = new AttributeTokenChain(); // Traverse through tree to get frequency
|
||||
|
||||
tree.walk(node => {
|
||||
if (!node.attrs) {
|
||||
return node;
|
||||
}
|
||||
|
||||
tokenchain.addFromNodeAttrs(node.attrs);
|
||||
return node;
|
||||
}); // Traverse through tree again, this time sort the attributes
|
||||
|
||||
tree.walk(node => {
|
||||
if (!node.attrs) {
|
||||
return node;
|
||||
}
|
||||
|
||||
node.attrs = tokenchain.sortFromNodeAttrs(node.attrs);
|
||||
return node;
|
||||
});
|
||||
return tree;
|
||||
}
|
||||
138
BACK_BACK/node_modules/htmlnano/lib/modules/sortAttributesWithLists.js
generated
vendored
Executable file
138
BACK_BACK/node_modules/htmlnano/lib/modules/sortAttributesWithLists.js
generated
vendored
Executable file
|
|
@ -0,0 +1,138 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = collapseAttributeWhitespace;
|
||||
|
||||
var _timsort = require("timsort");
|
||||
|
||||
var _collapseAttributeWhitespace = require("./collapseAttributeWhitespace");
|
||||
|
||||
// class, rel, ping
|
||||
const validOptions = new Set(['frequency', 'alphabetical']);
|
||||
|
||||
const processModuleOptions = options => {
|
||||
if (options === true) return 'alphabetical';
|
||||
return validOptions.has(options) ? options : false;
|
||||
};
|
||||
|
||||
class AttributeTokenChain {
|
||||
constructor() {
|
||||
this.freqData = new Map(); // <attrValue, frequency>[]
|
||||
}
|
||||
|
||||
addFromNodeAttrsArray(attrValuesArray) {
|
||||
attrValuesArray.forEach(attrValue => {
|
||||
if (this.freqData.has(attrValue)) {
|
||||
this.freqData.set(attrValue, this.freqData.get(attrValue) + 1);
|
||||
} else {
|
||||
this.freqData.set(attrValue, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createSortOrder() {
|
||||
let _sortOrder = [...this.freqData.entries()];
|
||||
(0, _timsort.sort)(_sortOrder, (a, b) => b[1] - a[1]);
|
||||
this.sortOrder = _sortOrder.map(i => i[0]);
|
||||
}
|
||||
|
||||
sortFromNodeAttrsArray(attrValuesArray) {
|
||||
const resultArray = [];
|
||||
|
||||
if (!this.sortOrder) {
|
||||
this.createSortOrder();
|
||||
}
|
||||
|
||||
this.sortOrder.forEach(k => {
|
||||
if (attrValuesArray.includes(k)) {
|
||||
resultArray.push(k);
|
||||
}
|
||||
});
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
}
|
||||
/** Sort values inside list-like attributes (e.g. class, rel) */
|
||||
|
||||
|
||||
function collapseAttributeWhitespace(tree, options, moduleOptions) {
|
||||
const sortType = processModuleOptions(moduleOptions);
|
||||
|
||||
if (sortType === 'alphabetical') {
|
||||
return sortAttributesWithListsInAlphabeticalOrder(tree);
|
||||
}
|
||||
|
||||
if (sortType === 'frequency') {
|
||||
return sortAttributesWithListsByFrequency(tree);
|
||||
} // Invalid configuration
|
||||
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
function sortAttributesWithListsInAlphabeticalOrder(tree) {
|
||||
tree.walk(node => {
|
||||
if (!node.attrs) {
|
||||
return node;
|
||||
}
|
||||
|
||||
Object.keys(node.attrs).forEach(attrName => {
|
||||
const attrNameLower = attrName.toLowerCase();
|
||||
|
||||
if (!_collapseAttributeWhitespace.attributesWithLists.has(attrNameLower)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const attrValues = node.attrs[attrName].split(/\s/);
|
||||
node.attrs[attrName] = attrValues.sort((a, b) => {
|
||||
return typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b;
|
||||
}).join(' ');
|
||||
});
|
||||
return node;
|
||||
});
|
||||
return tree;
|
||||
}
|
||||
|
||||
function sortAttributesWithListsByFrequency(tree) {
|
||||
const tokenChainObj = {}; // <attrNameLower: AttributeTokenChain>[]
|
||||
// Traverse through tree to get frequency
|
||||
|
||||
tree.walk(node => {
|
||||
if (!node.attrs) {
|
||||
return node;
|
||||
}
|
||||
|
||||
Object.entries(node.attrs).forEach(([attrName, attrValues]) => {
|
||||
const attrNameLower = attrName.toLowerCase();
|
||||
|
||||
if (!_collapseAttributeWhitespace.attributesWithLists.has(attrNameLower)) {
|
||||
return;
|
||||
}
|
||||
|
||||
tokenChainObj[attrNameLower] = tokenChainObj[attrNameLower] || new AttributeTokenChain();
|
||||
tokenChainObj[attrNameLower].addFromNodeAttrsArray(attrValues.split(/\s/));
|
||||
});
|
||||
return node;
|
||||
}); // Traverse through tree again, this time sort the attribute values
|
||||
|
||||
tree.walk(node => {
|
||||
if (!node.attrs) {
|
||||
return node;
|
||||
}
|
||||
|
||||
Object.entries(node.attrs).forEach(([attrName, attrValues]) => {
|
||||
const attrNameLower = attrName.toLowerCase();
|
||||
|
||||
if (!_collapseAttributeWhitespace.attributesWithLists.has(attrNameLower)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tokenChainObj[attrNameLower]) {
|
||||
node.attrs[attrName] = tokenChainObj[attrNameLower].sortFromNodeAttrsArray(attrValues.split(/\s/)).join(' ');
|
||||
}
|
||||
});
|
||||
return node;
|
||||
});
|
||||
}
|
||||
26
BACK_BACK/node_modules/htmlnano/lib/presets/ampSafe.js
generated
vendored
Executable file
26
BACK_BACK/node_modules/htmlnano/lib/presets/ampSafe.js
generated
vendored
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
|
||||
var _safe = _interopRequireDefault(require("./safe"));
|
||||
|
||||
function _interopRequireDefault(obj) {
|
||||
return obj && obj.__esModule ? obj : {
|
||||
default: obj
|
||||
};
|
||||
}
|
||||
/**
|
||||
* A safe preset for AMP pages (https://www.ampproject.org)
|
||||
*/
|
||||
|
||||
|
||||
var _default = { ..._safe.default,
|
||||
collapseBooleanAttributes: {
|
||||
amphtml: true
|
||||
},
|
||||
minifyJs: false
|
||||
};
|
||||
exports.default = _default;
|
||||
33
BACK_BACK/node_modules/htmlnano/lib/presets/max.js
generated
vendored
Executable file
33
BACK_BACK/node_modules/htmlnano/lib/presets/max.js
generated
vendored
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
|
||||
var _safe = _interopRequireDefault(require("./safe"));
|
||||
|
||||
function _interopRequireDefault(obj) {
|
||||
return obj && obj.__esModule ? obj : {
|
||||
default: obj
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Maximal minification (might break some pages)
|
||||
*/
|
||||
|
||||
|
||||
var _default = { ..._safe.default,
|
||||
collapseWhitespace: 'all',
|
||||
removeComments: 'all',
|
||||
removeAttributeQuotes: true,
|
||||
removeRedundantAttributes: true,
|
||||
removeUnusedCss: {},
|
||||
minifyCss: {
|
||||
preset: 'default'
|
||||
},
|
||||
minifySvg: {},
|
||||
minifyConditionalComments: true,
|
||||
removeOptionalTags: true
|
||||
};
|
||||
exports.default = _default;
|
||||
44
BACK_BACK/node_modules/htmlnano/lib/presets/safe.js
generated
vendored
Executable file
44
BACK_BACK/node_modules/htmlnano/lib/presets/safe.js
generated
vendored
Executable file
|
|
@ -0,0 +1,44 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
/**
|
||||
* Minify HTML in a safe way without breaking anything.
|
||||
*/
|
||||
|
||||
var _default = {
|
||||
sortAttributes: false,
|
||||
collapseAttributeWhitespace: true,
|
||||
collapseBooleanAttributes: {
|
||||
amphtml: false
|
||||
},
|
||||
collapseWhitespace: 'conservative',
|
||||
custom: [],
|
||||
deduplicateAttributeValues: true,
|
||||
mergeScripts: true,
|
||||
mergeStyles: true,
|
||||
removeUnusedCss: false,
|
||||
minifyCss: {
|
||||
preset: 'default'
|
||||
},
|
||||
minifyJs: {},
|
||||
minifyJson: {},
|
||||
minifySvg: {
|
||||
plugins: [{
|
||||
collapseGroups: false
|
||||
}, {
|
||||
convertShapeToPath: false
|
||||
}]
|
||||
},
|
||||
minifyConditionalComments: false,
|
||||
removeEmptyAttributes: true,
|
||||
removeRedundantAttributes: false,
|
||||
removeComments: 'safe',
|
||||
removeAttributeQuotes: false,
|
||||
sortAttributesWithLists: 'alphabetical',
|
||||
minifyUrls: false,
|
||||
removeOptionalTags: false
|
||||
};
|
||||
exports.default = _default;
|
||||
Loading…
Add table
Add a link
Reference in a new issue