Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Evaluate some String and Array instance methods at compile time #505

Merged
merged 11 commits into from
Jun 13, 2017
37 changes: 34 additions & 3 deletions packages/babel-plugin-minify-constant-folding/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# babel-plugin-minify-constant-folding

Tries to evaluate expressions and inline the result. For now only deals with numbers and strings.
Tries to evaluate expressions and inline the result.

## Example

Expand All @@ -10,7 +10,22 @@ Tries to evaluate expressions and inline the result. For now only deals with num
"a" + "b"
2 * 3;
4 | 3;
"b" + a + "c" + "d" + g + z + "f" + "h" + "z"
"b" + a + "c" + "d" + g + z + "f" + "h" + "i"

[a, b, c].concat([d, e], f, g, [h]);
["a", "b", "c"].join();
["a", "b", "c"].join('@');
[1, 2, 3].length;
[1, 2, 3][1];
[1, 2, 3].shift();
[1, 2, 3].slice(0, 2);
[a, b, c].pop();
[a, b, c].reverse();
"a,b,c".split(",");
"abc"[0];
"abc".charAt();
"abc".charAt(1);
"abc".length;
```

**Out**
Expand All @@ -19,7 +34,23 @@ Tries to evaluate expressions and inline the result. For now only deals with num
"ab";
6;
7;
"b" + a + "cd" + g + z + "fhz";
"b" + a + "cd" + g + z + "fhi";

[a, b, c, d, e, f, g, h];
"a,b,c";
"a@b@c";
3;
2;
2;
[1, 2];
c;
[c, b, a];
["a", "b", "c"];
"a";
"a";
"a";
"b";
3;
```

## Installation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,142 @@ describe("constant-folding-plugin", () => {
`);
expect(transform(source)).toBe(expected);
});

it("should handle Array methods on array literals", () => {
const source = unpad(
`
[1, 2, 3].concat([4, 5, 6]);
[a, b, c].concat([d, e], f, g, [h]);
[1, 2, 3]["concat"]([4, 5, 6]);
[1, 2, 3].push([4, 5, 6]);

[1, 2, 3].join();
["a", "b", "c"].join();
["a", "b", "c"].join("@");

[1, 2, 3].length;
[1, 2, 3][1];
[1, 2, 3]["1"];
[1, 2, 3][4];

[].shift();
[1, 2, 3].shift();

[1, 2, 3].slice();
[1, 2, 3].slice(1);
[1, 2, 3].slice(0, 2);
[1, 2, 3].slice(0, -1);

[1, 2, 3].pop();
[a, b, c].pop();
[].pop();

[a, b, c].reverse();
[1, 2, 3].reverse();

[1, 2, 3].splice(1);
[1, 2, 3, 4].splice(1, 2);
`
);
const expected = unpad(
`
[1, 2, 3, 4, 5, 6];
[a, b, c, d, e, f, g, h];
[1, 2, 3, 4, 5, 6];
4;

"1,2,3";
"a,b,c";
"a@b@c";

3;
2;
2;
void 0;

void 0;
2;

[1, 2, 3];
[2, 3];
[1, 2];
[1, 2, 3].slice(0, -1);

3;
c;
void 0;

[c, b, a];
[3, 2, 1];

[2, 3];
[2, 3];
`
);
expect(transform(source)).toBe(expected);
});
it("should ignore bad calls to array expression methods", () => {
const source = unpad(
`
[1, 2, 3][concat]([4, 5, 6]);
[a, "b", "c"].join();
["a", "b", "c"].join(a);
[1, 2, 3].splice("a");
`
);
expect(transform(source)).toBe(source);
});
it("should ignore bad calls to string expression methods", () => {
const source = unpad(
`
"abc".something;
"abc"["something"];
`
);
expect(transform(source)).toBe(source);
});
it("should handle String methods on string literals", () => {
const source = unpad(
`
"a,b,c".split(",");
"a,b,c".split("");
"a,b,c".split();
"abc"[0];
"abc"["0"];
"abc"[4];
"abc".charAt();
"abc".charAt(1);
"abc".charCodeAt();
"abc".charCodeAt(1);
"abc".length;

"\u{1f44d}".charCodeAt();
"\u{1f44d}".charCodeAt(1);
"\u{1f44d}".codePointAt();
"\u{1f44d}".codePointAt(1);
`
);

const expected = unpad(
`
["a", "b", "c"];
["a", ",", "b", ",", "c"];
["a,b,c"];
"a";
"a";
void 0;
"a";
"b";
97;
98;
3;

${0xd83d};
${0xdc4d};
${0x1f44d};
${0xdc4d};
`
);
expect(transform(source)).toBe(expected);
});
});
54 changes: 53 additions & 1 deletion packages/babel-plugin-minify-constant-folding/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,45 @@

const evaluate = require("babel-helper-evaluate-path");

module.exports = ({ types: t, traverse }) => {
const { FALLBACK_HANDLER } = require("./replacements");

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);
if (key === undefined) return;
let handler = handlers[key];
if (typeof handler !== "function") {
if (typeof handlers[FALLBACK_HANDLER] === "function") {
handler = handlers[FALLBACK_HANDLER].bind(member.object, key);
} else {
return false;
}
}
const replacement = handler.apply(member.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 { types: t, traverse } = babel;

return {
name: "minify-constant-folding",
Expand Down Expand Up @@ -124,6 +161,21 @@ module.exports = ({ types: t, traverse }) => {
node[seen] = true;
path.replaceWith(node);
}
},
CallExpression(path) {
const { node } = path;
const { callee: member } = node;
if (t.isMemberExpression(member)) {
const helpers = replacements[member.object.type];
if (!helpers || !helpers.calls) return;
swap(path, member, helpers.calls, ...node.arguments);
}
},
MemberExpression(path) {
const { node: member } = path;
const helpers = replacements[member.object.type];
if (!helpers || !helpers.members) return;
swap(path, member, helpers.members);
}
}
};
Expand Down
Loading