mirror of
https://github.com/S2-/minifyfromhtml.git
synced 2025-08-03 12:20:04 +02:00
230 lines
6.0 KiB
JavaScript
230 lines
6.0 KiB
JavaScript
"use strict";
|
|
|
|
const evaluate = require("babel-helper-evaluate-path");
|
|
|
|
const _require = require("./replacements"),
|
|
FALLBACK_HANDLER = _require.FALLBACK_HANDLER;
|
|
|
|
function getName(member) {
|
|
if (member.computed) {
|
|
switch (member.property.type) {
|
|
case "StringLiteral":
|
|
case "NumericLiteral":
|
|
return member.property.value;
|
|
|
|
case "TemplateLiteral":
|
|
return;
|
|
}
|
|
} else {
|
|
return member.property.name;
|
|
}
|
|
}
|
|
|
|
function swap(path, member, handlers, ...args) {
|
|
const key = getName(member.node);
|
|
if (key === void 0) return false;
|
|
let handler;
|
|
|
|
if (hop(handlers, key) && typeof handlers[key] === "function") {
|
|
handler = handlers[key];
|
|
} else if (typeof handlers[FALLBACK_HANDLER] === "function") {
|
|
handler = handlers[FALLBACK_HANDLER].bind(member.get("object"), key);
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
const replacement = handler.apply(member.get("object"), args);
|
|
|
|
if (replacement) {
|
|
path.replaceWith(replacement);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
module.exports = babel => {
|
|
const replacements = require("./replacements.js")(babel);
|
|
|
|
const seen = Symbol("seen");
|
|
const t = babel.types;
|
|
return {
|
|
name: "minify-constant-folding",
|
|
visitor: {
|
|
// Evaluate string expressions that are next to each other
|
|
// but are not actually a binary expression.
|
|
// "a" + b + "c" + "d" -> "a" + b + "cd"
|
|
BinaryExpression(path) {
|
|
if (!path.isBinaryExpression({
|
|
operator: "+"
|
|
})) {
|
|
return;
|
|
}
|
|
|
|
let literal, bin;
|
|
const left = path.get("left");
|
|
const right = path.get("right");
|
|
|
|
if (right.isStringLiteral()) {
|
|
literal = right;
|
|
|
|
if (left.isBinaryExpression({
|
|
operator: "+"
|
|
})) {
|
|
bin = left;
|
|
} else {
|
|
return;
|
|
}
|
|
} else if (left.isStringLiteral()) {
|
|
literal = left;
|
|
|
|
if (right.isBinaryExpression({
|
|
operator: "+"
|
|
})) {
|
|
bin = right;
|
|
} else {
|
|
return;
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
const relevant = getLeaf(bin, literal.key);
|
|
|
|
if (!relevant) {
|
|
return;
|
|
}
|
|
|
|
const value = literal.key === "right" ? relevant.node.value + literal.node.value : literal.node.value + relevant.node.value;
|
|
relevant.replaceWith(t.stringLiteral(value));
|
|
path.replaceWith(bin.node);
|
|
|
|
function getLeaf(path, direction) {
|
|
if (path.isStringLiteral()) {
|
|
return path;
|
|
} else if (path.isBinaryExpression({
|
|
operator: "+"
|
|
})) {
|
|
return getLeaf(path.get(direction), direction);
|
|
}
|
|
}
|
|
},
|
|
|
|
// TODO: look into evaluating binding too (could result in more code, but gzip?)
|
|
Expression(path, {
|
|
opts: {
|
|
tdz = false
|
|
} = {}
|
|
}) {
|
|
const node = path.node,
|
|
parent = path.parent;
|
|
|
|
if (node[seen]) {
|
|
return;
|
|
}
|
|
|
|
if (path.isLiteral()) {
|
|
return;
|
|
}
|
|
|
|
if (!path.isPure()) {
|
|
return;
|
|
} // Avoid replacing the values for identifiers in exports
|
|
|
|
|
|
if (t.isExportSpecifier(parent)) {
|
|
return;
|
|
} // -0 maybe compared via dividing and then checking against -Infinity
|
|
// Also -X will always be -X.
|
|
|
|
|
|
if (t.isUnaryExpression(node, {
|
|
operator: "-"
|
|
}) && t.isNumericLiteral(node.argument)) {
|
|
return;
|
|
} // We have a transform that converts true/false to !0/!1
|
|
|
|
|
|
if (t.isUnaryExpression(node, {
|
|
operator: "!"
|
|
}) && t.isNumericLiteral(node.argument)) {
|
|
if (node.argument.value === 0 || node.argument.value === 1) {
|
|
return;
|
|
}
|
|
} // void 0 is used for undefined.
|
|
|
|
|
|
if (t.isUnaryExpression(node, {
|
|
operator: "void"
|
|
}) && t.isNumericLiteral(node.argument, {
|
|
value: 0
|
|
})) {
|
|
return;
|
|
}
|
|
|
|
const res = evaluate(path, {
|
|
tdz
|
|
});
|
|
|
|
if (res.confident) {
|
|
// Avoid fractions because they can be longer than the original expression.
|
|
// There is also issues with number precision?
|
|
if (typeof res.value === "number" && !Number.isInteger(res.value)) {
|
|
return;
|
|
} // Preserve -0
|
|
|
|
|
|
if (typeof res.value === "number" && res.value === 0) {
|
|
if (1 / res.value === -Infinity) {
|
|
const node = t.unaryExpression("-", t.numericLiteral(0), true);
|
|
node[seen] = true;
|
|
path.replaceWith(node);
|
|
return;
|
|
}
|
|
} // this will convert object to object but
|
|
// t.valueToNode has other effects where property name
|
|
// is not treated for the respective environment.
|
|
// So we bail here for objects and let other plugins
|
|
// take care of converting String literal to Identifier
|
|
|
|
|
|
if (typeof res.value === "object") {
|
|
return;
|
|
}
|
|
|
|
const node = t.valueToNode(res.value);
|
|
node[seen] = true;
|
|
path.replaceWith(node);
|
|
}
|
|
},
|
|
|
|
CallExpression(path) {
|
|
const node = path.node;
|
|
const member = path.get("callee");
|
|
|
|
if (t.isMemberExpression(member)) {
|
|
const helpers = replacements[member.node.object.type];
|
|
if (!helpers || !helpers.calls) return; // find if the input can be constant folded
|
|
|
|
if (typeof helpers.canReplace === "function" && !helpers.canReplace.call(member.get("object"))) {
|
|
return;
|
|
}
|
|
|
|
swap(path, member, helpers.calls, ...node.arguments);
|
|
}
|
|
},
|
|
|
|
MemberExpression(path) {
|
|
const node = path.node;
|
|
const helpers = replacements[node.object.type];
|
|
if (!helpers || !helpers.members) return;
|
|
swap(path, path, helpers.members);
|
|
}
|
|
|
|
}
|
|
};
|
|
};
|
|
|
|
function hop(o, key) {
|
|
return Object.prototype.hasOwnProperty.call(o, key);
|
|
} |