Skip to content
This repository was archived by the owner on Apr 29, 2021. It is now read-only.

Commit 4277811

Browse files
committed
Add conditional expression optimizations
And a _ton_ of runtime tests to make sure I get it right
1 parent 1266d90 commit 4277811

File tree

3 files changed

+127
-4
lines changed

3 files changed

+127
-4
lines changed

Diff for: src/helpers/is-child-element.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,16 @@ function ancestorPath(path, useFastRoot) {
5252
if (last !== child) {
5353
continue;
5454
}
55-
} else if (path.isConditionalExpression() || path.isLogicalExpression()) {
56-
// These expressions "extend" the search, but they do not count as direct children.
57-
// That's because the expression could resolve to something other than a JSX element.
55+
} else if (path.isConditionalExpression()) {
5856
continue;
57+
} else if (path.isLogicalExpression()) {
58+
if (path.get("right") === last || path.node.operator === "||") {
59+
// These expressions "extend" the search, but they do not count as direct children.
60+
// That's because the expression could resolve to something other than a JSX element.
61+
continue;
62+
}
63+
64+
break;
5965
} else if (!useFastRoot) {
6066
// In normal mode, nothing else keeps a JSX search going.
6167
return;

Diff for: test/fixtures/conditional-optimizations/eval.js

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { transform } from "babel-core";
2+
import assert from "assert";
3+
import plugin from "../../../src/index";
4+
5+
const cases = [
6+
[ false, false, false, false ],
7+
[ false, false, false, true ],
8+
[ false, false, true, false ],
9+
[ false, false, true, true ],
10+
[ false, true, false, false ],
11+
[ false, true, false, true ],
12+
[ false, true, true, false ],
13+
[ false, true, true, true ],
14+
[ true, false, false, false ],
15+
[ true, false, false, true ],
16+
[ true, false, true, false ],
17+
[ true, false, true, true ],
18+
[ true, true, false, false ],
19+
[ true, true, false, true ],
20+
[ true, true, true, false ],
21+
[ true, true, true, true ],
22+
];
23+
const operations = [
24+
['||', '||', '||'],
25+
['||', '||', '&&'],
26+
['||', '&&', '||'],
27+
['||', '&&', '&&'],
28+
['&&', '||', '||'],
29+
['&&', '||', '&&'],
30+
['&&', '&&', '||'],
31+
['&&', '&&', '&&'],
32+
];
33+
const groupings = [
34+
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
35+
[' ', ' ', ' ', ' ', '(', ' ', ' ', ')'],
36+
[' ', ' ', '(', ' ', ' ', ')', ' ', ' '],
37+
['(', ' ', ' ', ')', ' ', ' ', ' ', ' '],
38+
['(', ' ', ' ', ')', '(', ' ', ' ', ')'],
39+
[' ', ' ', '(', ' ', ' ', ' ', ' ', ')'],
40+
['(', ' ', ' ', ' ', ' ', ')', ' ', ' '],
41+
];
42+
43+
44+
let wrappers = 0;
45+
let passes = 0;
46+
function elementOpen() { }
47+
function elementClose() { }
48+
function renderArbitrary() { }
49+
function elementVoid() { }
50+
function jsxWrapper() {
51+
wrappers++;
52+
return 'wrapper';
53+
}
54+
function pass() {
55+
passes++;
56+
return 'pass';
57+
}
58+
59+
function mockRequire() {
60+
return { renderArbitrary, jsxWrapper };
61+
}
62+
63+
for (let i = 0; i < cases.length; i++) {
64+
const c = cases[i];
65+
for (let j = 0; j < c.length; j++) {
66+
const bool = c.slice();
67+
const jsx = c.slice();
68+
bool.splice(j, 1, 'pass()');
69+
jsx.splice(j, 1, '<div />');
70+
71+
72+
for (let k = 0; k < operations.length; k++) {
73+
const operation = operations[k];
74+
for (var l = 0; l < groupings.length; l++) {
75+
const grouping = groupings[l];
76+
77+
test(bool, jsx, operation, grouping);
78+
}
79+
}
80+
}
81+
}
82+
83+
function test(bool, jsx, operation, grouping) {
84+
const boolCode = `${grouping[0]}${bool[0]}${grouping[1]} ${operation[0]} ${grouping[2]}${bool[1]}${grouping[3]} ${operation[1]} ${grouping[4]}${bool[2]}${grouping[5]} ${operation[2]} ${grouping[6]}${bool[3]}${grouping[7]}`;
85+
const jsxCode = `${grouping[0]}${ jsx[0]}${grouping[1]} ${operation[0]} ${grouping[2]}${ jsx[1]}${grouping[3]} ${operation[1]} ${grouping[4]}${ jsx[2]}${grouping[5]} ${operation[2]} ${grouping[6]}${ jsx[3]}${grouping[7]}`;
86+
87+
it(jsxCode, () => {
88+
wrappers = 0;
89+
passes = 0;
90+
91+
// When evaluating the case, the return value tells us two things.
92+
// If the return value is 1
93+
// - one elementVoid should have been called
94+
// - the evaluation of the jsx code should return "undefined" and not a jsx wrapper
95+
// If the return value is false
96+
// - No elementVoid calls
97+
// - the evaluation of the jsx code should return "false"
98+
const expected = eval(boolCode);
99+
const transformed = transform(`function render() { return <div>{${jsxCode}}</div>; }; render()`, {
100+
plugins: [
101+
'transform-es2015-modules-commonjs',
102+
[plugin, {runtimeModuleSource: 'test'}]
103+
]
104+
}).code;
105+
106+
const require = mockRequire;
107+
const result = eval(transformed);
108+
109+
if (expected === 'pass') {
110+
assert.equal(wrappers, 0, 'wrapper was created');
111+
} else {
112+
assert.equal(wrappers, passes, passes === 1 ? 'wrapper was not created' : 'wrapper was created');
113+
}
114+
});
115+
}

Diff for: test/index.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ function test(dir) {
8080

8181
function findTests(root) {
8282
const files = fs.readdirSync(root);
83-
if (files.indexOf("actual.js") > -1) {
83+
if (files.indexOf("eval.js") > -1) {
84+
require(path.join(root, "eval.js"));
85+
} else if (files.indexOf("actual.js") > -1) {
8486
it(path.basename(root), () => test(root));
8587
} else {
8688
files.forEach((file) => {

0 commit comments

Comments
 (0)