diff --git a/src/babel/transformation/file/index.js b/src/babel/transformation/file/index.js index 71598a46a7..050bf00053 100644 --- a/src/babel/transformation/file/index.js +++ b/src/babel/transformation/file/index.js @@ -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"); diff --git a/src/babel/transformation/helpers/build-react-transformer.js b/src/babel/transformation/helpers/build-react-transformer.js index d2d975dfa2..1d2653cddd 100644 --- a/src/babel/transformation/helpers/build-react-transformer.js +++ b/src/babel/transformation/helpers/build-react-transformer.js @@ -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) { diff --git a/src/babel/transformation/helpers/react.js b/src/babel/transformation/helpers/react.js index 25ea7f9dba..59a2c238d5 100644 --- a/src/babel/transformation/helpers/react.js +++ b/src/babel/transformation/helpers/react.js @@ -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); +} diff --git a/src/babel/transformation/templates/helper-default-props.js b/src/babel/transformation/templates/helper-default-props.js new file mode 100644 index 0000000000..4b2f679253 --- /dev/null +++ b/src/babel/transformation/templates/helper-default-props.js @@ -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; +}) diff --git a/src/babel/transformation/transformers/index.js b/src/babel/transformation/transformers/index.js index 3b7cbbc842..13254736e9 100644 --- a/src/babel/transformation/transformers/index.js +++ b/src/babel/transformation/transformers/index.js @@ -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"), diff --git a/src/babel/transformation/transformers/optimisation/react.inline-elements.js b/src/babel/transformation/transformers/optimisation/react.inline-elements.js new file mode 100644 index 0000000000..aa80f6589d --- /dev/null +++ b/src/babel/transformation/transformers/optimisation/react.inline-elements.js @@ -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; +} diff --git a/test/core/fixtures/transformation/optimisation.react.constant-elements/inline-elements/actual.js b/test/core/fixtures/transformation/optimisation.react.constant-elements/inline-elements/actual.js new file mode 100644 index 0000000000..d667da507e --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.constant-elements/inline-elements/actual.js @@ -0,0 +1,10 @@ +function render() { + return ; +} + +function render() { + var text = getText(); + return function () { + return {text}; + }; +} diff --git a/test/core/fixtures/transformation/optimisation.react.constant-elements/inline-elements/expected.js b/test/core/fixtures/transformation/optimisation.react.constant-elements/inline-elements/expected.js new file mode 100644 index 0000000000..1e3bf1d92d --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.constant-elements/inline-elements/expected.js @@ -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; + }; +} diff --git a/test/core/fixtures/transformation/optimisation.react.constant-elements/inline-elements/options.json b/test/core/fixtures/transformation/optimisation.react.constant-elements/inline-elements/options.json new file mode 100644 index 0000000000..d97a78ca64 --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.constant-elements/inline-elements/options.json @@ -0,0 +1,3 @@ +{ + "optional": ["optimisation.react.constantElements", "optimisation.react.inlineElements"] +} diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/component-with-props/actual.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/component-with-props/actual.js new file mode 100644 index 0000000000..bd2dc9d212 --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/component-with-props/actual.js @@ -0,0 +1 @@ +; diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/component-with-props/expected.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/component-with-props/expected.js new file mode 100644 index 0000000000..fae9f383ff --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/component-with-props/expected.js @@ -0,0 +1,11 @@ +"use strict"; + +({ + type: Baz, + ref: null, + children: [], + props: babelHelpers.defaultProps(Baz.defaultProps, { + foo: "bar" + }), + key: null +}); \ No newline at end of file diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/component/actual.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/component/actual.js new file mode 100644 index 0000000000..9938bca712 --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/component/actual.js @@ -0,0 +1 @@ +; diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/component/expected.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/component/expected.js new file mode 100644 index 0000000000..9ee63d21ad --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/component/expected.js @@ -0,0 +1,9 @@ +"use strict"; + +({ + type: Baz, + ref: null, + children: [], + props: babelHelpers.defaultProps(Baz.defaultProps, {}), + key: null +}); \ No newline at end of file diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/html-element-with-props/actual.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/html-element-with-props/actual.js new file mode 100644 index 0000000000..368764d412 --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/html-element-with-props/actual.js @@ -0,0 +1 @@ +; diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/html-element-with-props/expected.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/html-element-with-props/expected.js new file mode 100644 index 0000000000..a6dc3e0a4b --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/html-element-with-props/expected.js @@ -0,0 +1,11 @@ +"use strict"; + +({ + type: "foo", + ref: null, + children: [], + props: { + bar: "foo" + }, + key: null +}); \ No newline at end of file diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/html-element/actual.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/html-element/actual.js new file mode 100644 index 0000000000..64f88bcec8 --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/html-element/actual.js @@ -0,0 +1 @@ +; diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/html-element/expected.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/html-element/expected.js new file mode 100644 index 0000000000..f0b9752862 --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/html-element/expected.js @@ -0,0 +1,9 @@ +"use strict"; + +({ + type: "foo", + ref: null, + children: [], + props: {}, + key: null +}); \ No newline at end of file diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/key/actual.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/key/actual.js new file mode 100644 index 0000000000..f85670ba0b --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/key/actual.js @@ -0,0 +1 @@ + diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/key/expected.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/key/expected.js new file mode 100644 index 0000000000..f3ceb79ca1 --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/key/expected.js @@ -0,0 +1,8 @@ +"use strict"; + +({ + type: Foo, + ref: null, + props: babelHelpers.defaultProps(Foo.defaultProps, {}), + key: "foo" +}); \ No newline at end of file diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/nested-components/actual.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/nested-components/actual.js new file mode 100644 index 0000000000..e49f5b3d4e --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/nested-components/actual.js @@ -0,0 +1 @@ +{bar} diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/nested-components/expected.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/nested-components/expected.js new file mode 100644 index 0000000000..405c538c03 --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/nested-components/expected.js @@ -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 +}); diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/nested-html-elements/actual.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/nested-html-elements/actual.js new file mode 100644 index 0000000000..ede92d4a8b --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/nested-html-elements/actual.js @@ -0,0 +1 @@ +
{bar}
; diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/nested-html-elements/expected.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/nested-html-elements/expected.js new file mode 100644 index 0000000000..fa75e4186a --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/nested-html-elements/expected.js @@ -0,0 +1,11 @@ +"use strict"; + +({ + type: "div", + ref: null, + children: [bar], + props: { + className: "foo" + }, + key: null +}); \ No newline at end of file diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/nested/actual.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/nested/actual.js new file mode 100644 index 0000000000..3da240dd9d --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/nested/actual.js @@ -0,0 +1 @@ +
{bar}
diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/nested/expected.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/nested/expected.js new file mode 100644 index 0000000000..b741025c7f --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/nested/expected.js @@ -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 +}); \ No newline at end of file diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/options.json b/test/core/fixtures/transformation/optimisation.react.inline-elements/options.json new file mode 100644 index 0000000000..a0efc4990e --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/options.json @@ -0,0 +1,5 @@ +{ + "externalHelpers": true, + "noCheckAst": true, + "optional": ["optimisation.react.inlineElements"] +} diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/ref-deopt/actual.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/ref-deopt/actual.js new file mode 100644 index 0000000000..288a4e079f --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/ref-deopt/actual.js @@ -0,0 +1 @@ + diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/ref-deopt/expected.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/ref-deopt/expected.js new file mode 100644 index 0000000000..6a17a6636a --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/ref-deopt/expected.js @@ -0,0 +1,3 @@ +"use strict"; + +React.createElement(Foo, { ref: "bar" }); \ No newline at end of file diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-component-with-props/actual.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-component-with-props/actual.js new file mode 100644 index 0000000000..5e322cf4f6 --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-component-with-props/actual.js @@ -0,0 +1 @@ +; diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-component-with-props/expected.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-component-with-props/expected.js new file mode 100644 index 0000000000..05d9b4afde --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-component-with-props/expected.js @@ -0,0 +1,10 @@ +"use strict"; + +({ + type: Baz, + ref: null, + props: babelHelpers.defaultProps(Baz.defaultProps, { + foo: "bar" + }), + key: null +}); \ No newline at end of file diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-component/actual.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-component/actual.js new file mode 100644 index 0000000000..99a377b047 --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-component/actual.js @@ -0,0 +1 @@ +; diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-component/expected.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-component/expected.js new file mode 100644 index 0000000000..eebbcef988 --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-component/expected.js @@ -0,0 +1,8 @@ +"use strict"; + +({ + type: Baz, + ref: null, + props: babelHelpers.defaultProps(Baz.defaultProps, {}), + key: null +}); \ No newline at end of file diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-html-element-with-props/actual.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-html-element-with-props/actual.js new file mode 100644 index 0000000000..37aea6e7f9 --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-html-element-with-props/actual.js @@ -0,0 +1 @@ +; diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-html-element-with-props/expected.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-html-element-with-props/expected.js new file mode 100644 index 0000000000..6e5aec9eec --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-html-element-with-props/expected.js @@ -0,0 +1,10 @@ +"use strict"; + +({ + type: "foo", + ref: null, + props: { + bar: "foo" + }, + key: null +}); \ No newline at end of file diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-html-element/actual.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-html-element/actual.js new file mode 100644 index 0000000000..f26f44f9bb --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-html-element/actual.js @@ -0,0 +1 @@ +; diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-html-element/expected.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-html-element/expected.js new file mode 100644 index 0000000000..87d6fdbd33 --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/self-closing-html-element/expected.js @@ -0,0 +1,8 @@ +"use strict"; + +({ + type: "foo", + ref: null, + props: {}, + key: null +}); \ No newline at end of file diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/spread-deopt/actual.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/spread-deopt/actual.js new file mode 100644 index 0000000000..93f5f3480f --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/spread-deopt/actual.js @@ -0,0 +1 @@ + diff --git a/test/core/fixtures/transformation/optimisation.react.inline-elements/spread-deopt/expected.js b/test/core/fixtures/transformation/optimisation.react.inline-elements/spread-deopt/expected.js new file mode 100644 index 0000000000..85b1df167a --- /dev/null +++ b/test/core/fixtures/transformation/optimisation.react.inline-elements/spread-deopt/expected.js @@ -0,0 +1,3 @@ +"use strict"; + +React.createElement(Foo, bar); \ No newline at end of file