add optimisation.react.constantElements transformer - facebook/react#3228

This commit is contained in:
Sebastian McKenzie 2015-03-30 01:22:45 +11:00
parent 3952eefd01
commit ca5daca5dd
38 changed files with 358 additions and 78 deletions

View File

@ -80,7 +80,8 @@ export default class File {
"object-destructuring-empty",
"temporal-undefined",
"temporal-assert-defined",
"self-global"
"self-global",
"default-props"
];
static options = require("./options");

View File

@ -147,20 +147,7 @@ export default function (exports, opts) {
exit(node) {
var callExpr = node.openingElement;
for (var i = 0; i < node.children.length; i++) {
var child = node.children[i];
if (t.isLiteral(child) && typeof child.value === "string") {
cleanJSXElementLiteralChild(child, callExpr.arguments);
continue;
} else if (t.isJSXEmptyExpression(child)) {
continue;
}
callExpr.arguments.push(child);
}
callExpr.arguments = flatten(callExpr.arguments);
callExpr.arguments = callExpr.arguments.concat(react.buildChildren(node));
if (callExpr.arguments.length >= 3) {
callExpr._prettyCall = true;
@ -170,69 +157,6 @@ export default function (exports, opts) {
}
};
var isStringLiteral = function (node) {
return t.isLiteral(node) && isString(node.value);
};
var flatten = function (args) {
var flattened = [];
var last;
for (var i = 0; i < args.length; i++) {
var arg = args[i];
if (isStringLiteral(arg) && isStringLiteral(last)) {
last.value += arg.value;
} else {
last = arg;
flattened.push(arg);
}
}
return flattened;
};
var cleanJSXElementLiteralChild = function (child, args) {
var lines = child.value.split(/\r\n|\n|\r/);
var lastNonEmptyLine = 0;
var i;
for (i = 0; i < lines.length; i++) {
if (lines[i].match(/[^ \t]/)) {
lastNonEmptyLine = i;
}
}
for (i = 0; i < lines.length; i++) {
var line = lines[i];
var isFirstLine = i === 0;
var isLastLine = i === lines.length - 1;
var isLastNonEmptyLine = i === lastNonEmptyLine;
// replace rendered whitespace tabs with spaces
var trimmedLine = line.replace(/\t/g, " ");
// trim whitespace touching a newline
if (!isFirstLine) {
trimmedLine = trimmedLine.replace(/^[ ]+/, "");
}
// trim whitespace touching an endline
if (!isLastLine) {
trimmedLine = trimmedLine.replace(/[ ]+$/, "");
}
if (trimmedLine) {
if (!isLastNonEmptyLine) {
trimmedLine += " ";
}
args.push(t.literal(trimmedLine));
}
}
};
// display names
var addDisplayName = function (id, call) {

View File

@ -1,3 +1,4 @@
import isString from "lodash/lang/isString";
import * as t from "../../types";
var isCreateClassCallExpression = t.buildMatchMemberExpression("React.createClass");
@ -24,3 +25,86 @@ export var isReactComponent = t.buildMatchMemberExpression("React.Component");
export function isCompatTag(tagName) {
return tagName && /^[a-z]|\-/.test(tagName);
}
function flattenChildren(args) {
var flattened = [];
var last;
for (var i = 0; i < args.length; i++) {
var arg = args[i];
if (isStringLiteral(arg) && isStringLiteral(last)) {
last.value += arg.value;
} else {
last = arg;
flattened.push(arg);
}
}
return flattened;
}
function isStringLiteral(node) {
return t.isLiteral(node) && isString(node.value);
}
function cleanJSXElementLiteralChild(child, args) {
var lines = child.value.split(/\r\n|\n|\r/);
var lastNonEmptyLine = 0;
var i;
for (i = 0; i < lines.length; i++) {
if (lines[i].match(/[^ \t]/)) {
lastNonEmptyLine = i;
}
}
for (i = 0; i < lines.length; i++) {
var line = lines[i];
var isFirstLine = i === 0;
var isLastLine = i === lines.length - 1;
var isLastNonEmptyLine = i === lastNonEmptyLine;
// replace rendered whitespace tabs with spaces
var trimmedLine = line.replace(/\t/g, " ");
// trim whitespace touching a newline
if (!isFirstLine) {
trimmedLine = trimmedLine.replace(/^[ ]+/, "");
}
// trim whitespace touching an endline
if (!isLastLine) {
trimmedLine = trimmedLine.replace(/[ ]+$/, "");
}
if (trimmedLine) {
if (!isLastNonEmptyLine) {
trimmedLine += " ";
}
args.push(t.literal(trimmedLine));
}
}
}
export function buildChildren(node) {
var elems = [];
for (var i = 0; i < node.children.length; i++) {
var child = node.children[i];
if (t.isJSXExpressionContainer(child)) child = child.expression;
if (t.isLiteral(child) && typeof child.value === "string") {
cleanJSXElementLiteralChild(child, elems);
continue;
} else if (t.isJSXEmptyExpression(child)) {
continue;
}
elems.push(child);
}
return flattenChildren(elems);
}

View File

@ -0,0 +1,10 @@
(function (defaultProps, props) {
if (defaultProps) {
for (var propName in defaultProps) {
if (typeof props[propName] === "undefined") {
props[propName] = defaultProps[propName];
}
}
}
return props;
})

View File

@ -19,6 +19,7 @@ export default {
"spec.blockScopedFunctions": require("./spec/block-scoped-functions"),
"optimisation.react.constantElements": require("./optimisation/react.constant-elements"),
"optimisation.react.inlineElements": require("./optimisation/react.inline-elements"),
reactCompat: require("./other/react-compat"),
react: require("./other/react"),

View File

@ -0,0 +1,70 @@
import * as react from "../../helpers/react";
import * as t from "../../../types";
export var metadata = {
optional: true
};
function hasRefOrSpread(attrs) {
for (var i = 0; i < attrs.length; i++) {
var attr = attrs[i];
if (t.isJSXSpreadAttribute(attr)) return true;
if (isJSXAttributeOfName(attr, "ref")) return true;
}
return false;
}
function isJSXAttributeOfName(attr, name) {
return t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name, { name: name });
}
export function JSXElement(node, parent, scope, file) {
// filter
var open = node.openingElement;
if (hasRefOrSpread(open.attributes)) return;
// init
var isComponent = true;
var props = t.objectExpression([]);
var obj = t.objectExpression([]);
var key = t.literal(null);
var type = open.name;
if (t.isJSXIdentifier(type) && react.isCompatTag(type.name)) {
type = t.literal(type.name);
isComponent = false;
}
function pushElemProp(key, value) {
obj.properties.push(t.property("init", t.identifier(key), value));
}
// metadata
pushElemProp("type", type);
pushElemProp("ref", t.literal(null));
if (!open.selfClosing) {
pushElemProp("children", t.arrayExpression(react.buildChildren(node)));
}
// props
for (var i = 0; i < open.attributes.length; i++) {
var attr = open.attributes[i];
if (isJSXAttributeOfName(attr, "key")) {
key = attr.value;
} else {
props.properties.push(t.property("init", attr.name, attr.value));
}
}
if (isComponent) {
props = t.callExpression(file.addHelper("default-props"), [t.memberExpression(type, t.identifier("defaultProps")), props]);
}
pushElemProp("props", props);
// key
pushElemProp("key", key);
return obj;
}

View File

@ -0,0 +1,10 @@
function render() {
return <foo />;
}
function render() {
var text = getText();
return function () {
return <foo>{text}</foo>;
};
}

View File

@ -0,0 +1,25 @@
"use strict";
var _ref = {
type: "foo",
ref: null,
props: {},
key: null
};
function render() {
return _ref;
}
function render() {
var text = getText();
var _ref = {
type: "foo",
ref: null,
children: [text],
props: {},
key: null
};
return function () {
return _ref;
};
}

View File

@ -0,0 +1,3 @@
{
"optional": ["optimisation.react.constantElements", "optimisation.react.inlineElements"]
}

View File

@ -0,0 +1,11 @@
"use strict";
({
type: Baz,
ref: null,
children: [],
props: babelHelpers.defaultProps(Baz.defaultProps, {
foo: "bar"
}),
key: null
});

View File

@ -0,0 +1,9 @@
"use strict";
({
type: Baz,
ref: null,
children: [],
props: babelHelpers.defaultProps(Baz.defaultProps, {}),
key: null
});

View File

@ -0,0 +1,11 @@
"use strict";
({
type: "foo",
ref: null,
children: [],
props: {
bar: "foo"
},
key: null
});

View File

@ -0,0 +1,9 @@
"use strict";
({
type: "foo",
ref: null,
children: [],
props: {},
key: null
});

View File

@ -0,0 +1 @@
<Foo key="foo" />

View File

@ -0,0 +1,8 @@
"use strict";
({
type: Foo,
ref: null,
props: babelHelpers.defaultProps(Foo.defaultProps, {}),
key: "foo"
});

View File

@ -0,0 +1 @@
<Foo className="foo">{bar}<Baz key="baz" /></Foo>

View File

@ -0,0 +1,16 @@
"use strict";
({
type: Foo,
ref: null,
children: [bar, {
type: Baz,
ref: null,
props: babelHelpers.defaultProps(Baz.defaultProps, {}),
key: "baz"
}],
props: babelHelpers.defaultProps(Foo.defaultProps, {
className: "foo"
}),
key: null
});

View File

@ -0,0 +1 @@
<div className="foo">{bar}</div>;

View File

@ -0,0 +1,11 @@
"use strict";
({
type: "div",
ref: null,
children: [bar],
props: {
className: "foo"
},
key: null
});

View File

@ -0,0 +1 @@
<div className="foo">{bar}<Baz key="baz" /></div>

View File

@ -0,0 +1,16 @@
"use strict";
({
type: "div",
ref: null,
children: [bar, {
type: Baz,
ref: null,
props: babelHelpers.defaultProps(Baz.defaultProps, {}),
key: "baz"
}],
props: {
className: "foo"
},
key: null
});

View File

@ -0,0 +1,5 @@
{
"externalHelpers": true,
"noCheckAst": true,
"optional": ["optimisation.react.inlineElements"]
}

View File

@ -0,0 +1,3 @@
"use strict";
React.createElement(Foo, { ref: "bar" });

View File

@ -0,0 +1,10 @@
"use strict";
({
type: Baz,
ref: null,
props: babelHelpers.defaultProps(Baz.defaultProps, {
foo: "bar"
}),
key: null
});

View File

@ -0,0 +1,8 @@
"use strict";
({
type: Baz,
ref: null,
props: babelHelpers.defaultProps(Baz.defaultProps, {}),
key: null
});

View File

@ -0,0 +1,10 @@
"use strict";
({
type: "foo",
ref: null,
props: {
bar: "foo"
},
key: null
});

View File

@ -0,0 +1,8 @@
"use strict";
({
type: "foo",
ref: null,
props: {},
key: null
});

View File

@ -0,0 +1,3 @@
"use strict";
React.createElement(Foo, bar);