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

Minify more type constructors #45

Merged
merged 3 commits into from
Jul 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/babel-plugin-minify-type-constructors/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# babel-plugin-minify-type-constructors

**Note:** Not recommended if full support for IE8 and lower is required. [Details](https://github.com/amasad/babel-minify/pull/45#discussion_r70181249)

## Installation

```sh
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,195 @@ describe("type-constructors-plugin", () => {
expect(transform(source)).toBe(expected);
});

it("should turn Array() to []", () => {
const source = "Array();";
const expected = "[];";
expect(transform(source)).toBe(expected);
});

it("should turn new Array() to []", () => {
const source = "new Array();";
const expected = "[];";
expect(transform(source)).toBe(expected);
});

it("should turn Array(nonNumericValue) to [nonNumericValue]", () => {
const source = unpad(`
Array("Rome");
Array(false);
Array(null);
Array({});
Array([]);
`);
const expected = unpad(`
["Rome"];
[false];
[null];
[{}];
[[]];
`);
expect(transform(source)).toBe(expected);
});

it("should turn Array(number) to [,] only if number is <=6", () => {
Copy link
Contributor Author

@fregante fregante Jul 10, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only issue here is oldIE: [,].length is 2 in IE8- and 1 everywhere else.

Specifically, none of these work correctly there:

Array(1);
Array(2);
Array(3);
Array(4);
Array(5);
Array(6);

Are you going to support IE8 anywhere else?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it's worth supporting IE8. But we need to at least mention this in the docs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd vote we document it for now, we could always have a separate transform to run through and pick out stuff like this that IE would choke on.

const source = unpad(`
Array(0);
Array(1);
Array(6);
Array(7);
`);
const expected = unpad(`
[];
[,];
[,,,,,,];
Array(7);
`);
expect(transform(source)).toBe(expected);
});

it("should turn new Array(number) to Array(number) if number is >6", () => {
const source = unpad(`
new Array(6);
new Array(7);
`);
const expected = unpad(`
[,,,,,,];
Array(7);
`);
expect(transform(source)).toBe(expected);
});

it("should turn Array(value, value) to [value, value]", () => {
const source = unpad(`
Array("a", "b");
new Array("0", "1", {});
Array(10, Symbol(), foo());
`);
const expected = unpad(`
["a", "b"];
["0", "1", {}];
[10, Symbol(), foo()];
`);
expect(transform(source)).toBe(expected);
});

it("should turn Object() to {}", () => {
const source = "var x = Object();";
const expected = "var x = {};";
expect(transform(source)).toBe(expected);
});

it("should turn new Object() to {}", () => {
const source = "var x = new Object();";
const expected = "var x = {};";
expect(transform(source)).toBe(expected);
});

it("should change Object(null|undefined) to {}", () => {
const source = unpad(`
[
Object(null),
Object(undefined),
new Object(void 0)
]
`);
const expected = "[{}, {}, {}];";
expect(transform(source)).toBe(expected);
});

it("should change Object({a:b}) to {a:b}", () => {
const source = unpad(`
[
Object({}),
Object({a:b}),
Object({a:b, c:d}),
]
`);// todo: add Object(Array())
const expected = "[{}, { a: b }, { a: b, c: d }];";
expect(transform(source)).toBe(expected);
});

it("should change Object([]) to []", () => {
const source = unpad(`
[
Object([]),
Object([1]),
Object([1,2]),
new Object([null])
]
`);// todo: add Object(Array())
const expected = "[[], [1], [1, 2], [null]];";
expect(transform(source)).toBe(expected);
});

it("should change Object(localFn) to localFn", () => {
const source = unpad(`
function a() {};
[
Object(function () {}),
new Object(a),
Object(Array)
]
`);
const expected = unpad(`
function a() {};
[function () {}, a, Object(Array)];
`);
expect(transform(source)).toBe(expected);
});

it("shouldn't change Object(value) for unrecognized values", () => {
const source = unpad(`
[
Object("undefined"),
Object(nulled),
Object(0),
Object(false),
Object(stuff())
]
`);
const expected = "[Object(\"undefined\"), Object(nulled), Object(0), Object(false), Object(stuff())];";
expect(transform(source)).toBe(expected);
});

it("should change new Object(value) to Object(value) for unrecognized values", () => {
const source = unpad(`
[
new Object("function"),
new Object(Symbol),
new Object(true),
new Object(1),
new Object(call({ me: true }))
]
`);
const expected = "[Object(\"function\"), Object(Symbol), Object(true), Object(1), Object(call({ me: true }))];";
expect(transform(source)).toBe(expected);
});

it("should change Object() to ({}) in ambiguous contexts", () => {
const source = unpad(`
new Object();
var foo = () => Object();
var bar = () => Object({ baz: 3 });
`);
const expected = unpad(`
({});
var foo = () => ({});
var bar = () => ({ baz: 3 });
`);
expect(transform(source)).toBe(expected);
});

it("shouldn't change referenced identifiers", () => {
const source = unpad(`
(function (Boolean, String, Number) {
return Boolean(a), String(b), Number(c);
})(MyBoolean, MyString, MyNumber);
(function (Boolean, String, Number, Array, Object) {
return Boolean(a), String(b), Number(c), Array(d), Object(d);
})(MyBoolean, MyString, MyNumber, MyArray, MyObject);
`);
const expected = unpad(`
(function (Boolean, String, Number) {
return Boolean(a), String(b), Number(c);
})(MyBoolean, MyString, MyNumber);
(function (Boolean, String, Number, Array, Object) {
return Boolean(a), String(b), Number(c), Array(d), Object(d);
})(MyBoolean, MyString, MyNumber, MyArray, MyObject);
`);
expect(transform(source)).toBe(expected);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"keywords": [
"babel-plugin"
],
"dependencies": {},
"dependencies": {
"babel-helper-is-void-0": "^0.0.0"
},
"devDependencies": {}
}
88 changes: 88 additions & 0 deletions packages/babel-plugin-minify-type-constructors/src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,72 @@
"use strict";

function replaceArray(t, path) {
const { node } = path;
if (t.isIdentifier(node.callee, { name: "Array" }) &&
!path.scope.getBinding("Array")) {

// Array(5) -> [,,,,,]
if (node.arguments.length === 1 &&
typeof node.arguments[0].value === "number") {

// "Array(7)" is shorter than "[,,,,,,,]"
if (node.arguments[0].value <= 6) {
path.replaceWith(t.arrayExpression(Array(node.arguments[0].value).fill(null)));

// new Array(7) -> Array(7)
} else if (node.type === "NewExpression") {
path.replaceWith(t.callExpression(node.callee, node.arguments));
}

// Array("Hello") -> ["Hello"]
} else {
path.replaceWith(t.arrayExpression(node.arguments));
}
return true;
}
}

function replaceObject(t, path) {
const { node } = path;
if (t.isIdentifier(node.callee, { name: "Object" }) &&
!path.scope.getBinding("Object")) {

const isVoid0 = require("babel-helper-is-void-0")(t);
const arg = node.arguments[0];
const binding = arg && t.isIdentifier(arg) && path.scope.getBinding(arg.name);

// Object() -> {}
if (node.arguments.length === 0) {
path.replaceWith(t.objectExpression([]));

// Object([]) -> []
} else if (arg.type === "ArrayExpression" ||
t.isFunctionExpression(arg)) {
path.replaceWith(arg);

// Object(null) -> {}
} else if (isVoid0(arg) ||
arg.name === "undefined" ||
arg.type === "NullLiteral" ||
arg.type === "ObjectExpression" && arg.properties.length === 0) {
path.replaceWith(t.objectExpression([]));

// Object(localFn) -> localFn
} else if (binding && binding.path.isFunction()) {
path.replaceWith(arg);

// Object({a:b}) -> {a:b}
} else if (arg.type === "ObjectExpression") {
path.replaceWith(arg);

// new Object(a) -> Object(a)
} else if (node.type === "NewExpression") {
path.replaceWith(t.callExpression(node.callee, node.arguments));
}
return true;
}
}

module.exports = function({ types: t }) {
return {
visitor: {
Expand Down Expand Up @@ -29,6 +96,27 @@ module.exports = function({ types: t }) {
path.replaceWith(t.binaryExpression("+", node.arguments[0], t.stringLiteral("")));
return;
}

// Array() -> []
if (replaceArray(t, path)) {
return;
}

// Object() -> {}
if (replaceObject(t, path)) {
return;
}
},
NewExpression(path) {
// new Array() -> []
if (replaceArray(t, path)) {
return;
}

// new Object() -> {}
if (replaceObject(t, path)) {
return;
}
},
},
};
Expand Down