From d1b8db1532d82b5d533732412b1bb9b9b6987251 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Thu, 12 Nov 2015 17:44:58 -0800 Subject: [PATCH] React inlining: Refactor to reduce parsing cost - Have the `jsx` helper do the `defaultProps` work instead of calling `defaultProps` inline. - Put `key` after `props` and make it optional. - Inline `children` as rest args instead of in the object. - Rename `createRawReactElement` to `jsx`. I wish I was kidding. Most of these are silly microoptimizations. In my test file (based off an internal RN app), this reduces the parsing overhead of inlining from around 1% to 0.1% in JSC and from 0.6% to 0.0% in V8 (compared to element inlining before this commit). Once parsed, the initial render with inlining is the same speed as not inlining in JSC and ~1% slower in V8. A second initial render in the same context (reusing the function objects, JIT, etc) is 2.0% faster in JSC and 5.5% faster in V8. --- packages/babel-helpers/src/helpers.js | 48 ++++++++++++------- .../inline-elements/expected.js | 8 ++-- .../src/index.js | 26 ++++------ .../children-exists/expected.js | 7 ++- .../component-with-props/expected.js | 4 +- .../inline-elements/component/expected.js | 2 +- .../expression-container/expected.js | 2 +- .../html-element-with-props/expected.js | 2 +- .../inline-elements/html-element/expected.js | 2 +- .../inline-elements/key-computed/expected.js | 4 +- .../fixtures/inline-elements/key/expected.js | 4 +- .../inline-elements/multiline/expected.js | 2 +- .../nested-components/expected.js | 7 ++- .../nested-html-elements/expected.js | 7 ++- .../inline-elements/nested/expected.js | 7 ++- .../expected.js | 4 +- .../self-closing-component/expected.js | 2 +- .../expected.js | 2 +- .../self-closing-html-element/expected.js | 2 +- .../shorthand-attributes/expected.js | 4 +- 20 files changed, 72 insertions(+), 74 deletions(-) diff --git a/packages/babel-helpers/src/helpers.js b/packages/babel-helpers/src/helpers.js index 6b081e8df2..3774425b70 100644 --- a/packages/babel-helpers/src/helpers.js +++ b/packages/babel-helpers/src/helpers.js @@ -9,15 +9,43 @@ helpers.typeof = template(` }); `); -helpers.createRawReactElement = template(` +helpers.jsx = template(` (function () { var REACT_ELEMENT_TYPE = (typeof Symbol === "function" && Symbol.for && Symbol.for("react.element")) || 0xeac7; - return function createRawReactElement (type, key, props) { + return function createRawReactElement (type, props, key, children) { + var defaultProps = type && type.defaultProps; + var childrenLength = arguments.length - 3; + + if (!props && childrenLength !== 0) { + // If we're going to assign props.children, we create a new object now + // to avoid mutating defaultProps. + props = {}; + } + if (props && defaultProps) { + for (var propName in defaultProps) { + if (props[propName] === void 0) { + props[propName] = defaultProps[propName]; + } + } + } else if (!props) { + props = defaultProps || {}; + } + + if (childrenLength === 1) { + props.children = children; + } else if (childrenLength > 1) { + var childArray = Array(childrenLength); + for (var i = 0; i < childrenLength; i++) { + childArray[i] = arguments[i + 3]; + } + props.children = childArray; + } + return { $$typeof: REACT_ELEMENT_TYPE, type: type, - key: key, + key: key === undefined ? null : '' + key, ref: null, props: props, _owner: null, @@ -87,20 +115,6 @@ helpers.createClass = template(` })() `); -helpers.defaultProps = template(` - (function (defaultProps, props) { - if (defaultProps) { - for (var propName in defaultProps) { - if (typeof props[propName] === "undefined") { - props[propName] = defaultProps[propName]; - } - } - } - return props; - }) -`); - - helpers.defineEnumerableProperties = template(` (function (obj, descs) { for (var key in descs) { diff --git a/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/inline-elements/expected.js b/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/inline-elements/expected.js index 39a276333f..172a587281 100644 --- a/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/inline-elements/expected.js +++ b/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/inline-elements/expected.js @@ -1,6 +1,6 @@ -var _createRawReactElement = (function () { var REACT_ELEMENT_TYPE = typeof Symbol === "function" && Symbol.for && Symbol.for("react.element") || 0xeac7; return function createRawReactElement(type, key, props) { return { $$typeof: REACT_ELEMENT_TYPE, type: type, key: key, ref: null, props: props, _owner: null }; }; })(); +var _jsx = (function () { var REACT_ELEMENT_TYPE = typeof Symbol === "function" && Symbol.for && Symbol.for("react.element") || 0xeac7; return function createRawReactElement(type, props, key, children) { var defaultProps = type && type.defaultProps; var childrenLength = arguments.length - 3; if (!props && childrenLength !== 0) { props = {}; } if (props && defaultProps) { for (var propName in defaultProps) { if (props[propName] === void 0) { props[propName] = defaultProps[propName]; } } } else if (!props) { props = defaultProps || {}; } if (childrenLength === 1) { props.children = children; } else if (childrenLength > 1) { var childArray = Array(childrenLength); for (var i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 3]; } props.children = childArray; } return { $$typeof: REACT_ELEMENT_TYPE, type: type, key: key === undefined ? null : '' + key, ref: null, props: props, _owner: null }; }; })(); -var _ref = _createRawReactElement("foo", null, {}); +var _ref = _jsx("foo", {}); function render() { return _ref; @@ -9,9 +9,7 @@ function render() { function render() { var text = getText(); - var _ref2 = _createRawReactElement("foo", null, { - children: text - }); + var _ref2 = _jsx("foo", {}, void 0, text); return function () { return _ref2; diff --git a/packages/babel-plugin-transform-react-inline-elements/src/index.js b/packages/babel-plugin-transform-react-inline-elements/src/index.js index 98bccdc51c..5f394fc3f8 100644 --- a/packages/babel-plugin-transform-react-inline-elements/src/index.js +++ b/packages/babel-plugin-transform-react-inline-elements/src/index.js @@ -29,14 +29,12 @@ export default function ({ types: t }) { if (hasRefOrSpread(open.attributes)) return; // init - let isComponent = true; let props = t.objectExpression([]); - let key = t.nullLiteral(); + let key = null; let type = open.name; if (t.isJSXIdentifier(type) && t.react.isCompatTag(type.name)) { type = t.stringLiteral(type.name); - isComponent = false; } function pushProp(objProps, key, value) { @@ -54,24 +52,16 @@ export default function ({ types: t }) { } } - if (node.children.length) { + let args = [type, props]; + if (key || node.children.length) { let children = t.react.buildChildren(node); - if (children.length) { - children = children.length === 1 ? children[0] : t.arrayExpression(children); - pushProp(props.properties, t.identifier("children"), children); - } + args.push( + key || t.unaryExpression("void", t.numericLiteral(0), true), + ...children + ); } - if (isComponent) { - let defProps = t.memberExpression(type, t.identifier("defaultProps")); - if (props.properties.length) { - props = t.callExpression(file.addHelper("defaultProps"), [defProps, props]); - } else { - props = t.logicalExpression("||", defProps, props); - } - } - - let el = t.callExpression(file.addHelper("createRawReactElement"), [type, key, props]); + let el = t.callExpression(file.addHelper("jsx"), args); path.replaceWith(el); } } diff --git a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/children-exists/expected.js b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/children-exists/expected.js index 9d51047ee1..39e744a5e6 100644 --- a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/children-exists/expected.js +++ b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/children-exists/expected.js @@ -1,4 +1,3 @@ -babelHelpers.createRawReactElement("div", null, { - children: "foo", - children: "bar" -}); \ No newline at end of file +babelHelpers.jsx("div", { + children: "foo" +}, void 0, "bar"); \ No newline at end of file diff --git a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/component-with-props/expected.js b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/component-with-props/expected.js index 667a95a77a..80212dbf35 100644 --- a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/component-with-props/expected.js +++ b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/component-with-props/expected.js @@ -1,3 +1,3 @@ -babelHelpers.createRawReactElement(Baz, null, babelHelpers.defaultProps(Baz.defaultProps, { +babelHelpers.jsx(Baz, { foo: "bar" -})); \ No newline at end of file +}); \ No newline at end of file diff --git a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/component/expected.js b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/component/expected.js index d5eadb5b14..c38a6d7fb8 100644 --- a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/component/expected.js +++ b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/component/expected.js @@ -1 +1 @@ -babelHelpers.createRawReactElement(Baz, null, Baz.defaultProps || {}); \ No newline at end of file +babelHelpers.jsx(Baz, {}); \ No newline at end of file diff --git a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/expression-container/expected.js b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/expression-container/expected.js index 6adbdc7bf0..36fdc25bc3 100644 --- a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/expression-container/expected.js +++ b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/expression-container/expected.js @@ -1,6 +1,6 @@ var TestComponent = React.createClass({ render: function () { - return babelHelpers.createRawReactElement("span", null, { + return babelHelpers.jsx("span", { className: this.props.someProp }); } diff --git a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/html-element-with-props/expected.js b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/html-element-with-props/expected.js index 9f767adf19..ca89ce967a 100644 --- a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/html-element-with-props/expected.js +++ b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/html-element-with-props/expected.js @@ -1,3 +1,3 @@ -babelHelpers.createRawReactElement("foo", null, { +babelHelpers.jsx("foo", { bar: "foo" }); \ No newline at end of file diff --git a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/html-element/expected.js b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/html-element/expected.js index dc4b3c9bea..f5b040ddc7 100644 --- a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/html-element/expected.js +++ b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/html-element/expected.js @@ -1 +1 @@ -babelHelpers.createRawReactElement("foo", null, {}); \ No newline at end of file +babelHelpers.jsx("foo", {}); \ No newline at end of file diff --git a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/key-computed/expected.js b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/key-computed/expected.js index bf6853e7eb..c860a3dac1 100644 --- a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/key-computed/expected.js +++ b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/key-computed/expected.js @@ -1,3 +1,3 @@ -babelHelpers.createRawReactElement(Foo, "foo" + "baz", babelHelpers.defaultProps(Foo.defaultProps, { +babelHelpers.jsx(Foo, { "data-value": "bar" -})); +}, "foo" + "baz"); \ No newline at end of file diff --git a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/key/expected.js b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/key/expected.js index d3c8589af3..0e1a1007bf 100644 --- a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/key/expected.js +++ b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/key/expected.js @@ -1,3 +1,3 @@ -babelHelpers.createRawReactElement(Foo, "foo", babelHelpers.defaultProps(Foo.defaultProps, { +babelHelpers.jsx(Foo, { "data-value": "bar" -})); \ No newline at end of file +}, "foo"); \ No newline at end of file diff --git a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/multiline/expected.js b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/multiline/expected.js index d5eadb5b14..7709b1b293 100644 --- a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/multiline/expected.js +++ b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/multiline/expected.js @@ -1 +1 @@ -babelHelpers.createRawReactElement(Baz, null, Baz.defaultProps || {}); \ No newline at end of file +babelHelpers.jsx(Baz, {}, void 0); \ No newline at end of file diff --git a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/nested-components/expected.js b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/nested-components/expected.js index 61ff4ecd4b..28514da367 100644 --- a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/nested-components/expected.js +++ b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/nested-components/expected.js @@ -1,4 +1,3 @@ -babelHelpers.createRawReactElement(Foo, null, babelHelpers.defaultProps(Foo.defaultProps, { - className: "foo", - children: [bar, babelHelpers.createRawReactElement(Baz, "baz", Baz.defaultProps || {})] -})); \ No newline at end of file +babelHelpers.jsx(Foo, { + className: "foo" +}, void 0, bar, babelHelpers.jsx(Baz, {}, "baz")); \ No newline at end of file diff --git a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/nested-html-elements/expected.js b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/nested-html-elements/expected.js index cdbb32778b..e247fb37c9 100644 --- a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/nested-html-elements/expected.js +++ b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/nested-html-elements/expected.js @@ -1,4 +1,3 @@ -babelHelpers.createRawReactElement("div", null, { - className: "foo", - children: bar -}); \ No newline at end of file +babelHelpers.jsx("div", { + className: "foo" +}, void 0, bar); \ No newline at end of file diff --git a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/nested/expected.js b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/nested/expected.js index 3a5eb10f31..166616d168 100644 --- a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/nested/expected.js +++ b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/nested/expected.js @@ -1,4 +1,3 @@ -babelHelpers.createRawReactElement("div", null, { - className: "foo", - children: [bar, babelHelpers.createRawReactElement(Baz, "baz", Baz.defaultProps || {})] -}); \ No newline at end of file +babelHelpers.jsx("div", { + className: "foo" +}, void 0, bar, babelHelpers.jsx(Baz, {}, "baz")); \ No newline at end of file diff --git a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/self-closing-component-with-props/expected.js b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/self-closing-component-with-props/expected.js index 667a95a77a..80212dbf35 100644 --- a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/self-closing-component-with-props/expected.js +++ b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/self-closing-component-with-props/expected.js @@ -1,3 +1,3 @@ -babelHelpers.createRawReactElement(Baz, null, babelHelpers.defaultProps(Baz.defaultProps, { +babelHelpers.jsx(Baz, { foo: "bar" -})); \ No newline at end of file +}); \ No newline at end of file diff --git a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/self-closing-component/expected.js b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/self-closing-component/expected.js index d5eadb5b14..c38a6d7fb8 100644 --- a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/self-closing-component/expected.js +++ b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/self-closing-component/expected.js @@ -1 +1 @@ -babelHelpers.createRawReactElement(Baz, null, Baz.defaultProps || {}); \ No newline at end of file +babelHelpers.jsx(Baz, {}); \ No newline at end of file diff --git a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/self-closing-html-element-with-props/expected.js b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/self-closing-html-element-with-props/expected.js index 9f767adf19..ca89ce967a 100644 --- a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/self-closing-html-element-with-props/expected.js +++ b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/self-closing-html-element-with-props/expected.js @@ -1,3 +1,3 @@ -babelHelpers.createRawReactElement("foo", null, { +babelHelpers.jsx("foo", { bar: "foo" }); \ No newline at end of file diff --git a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/self-closing-html-element/expected.js b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/self-closing-html-element/expected.js index dc4b3c9bea..f5b040ddc7 100644 --- a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/self-closing-html-element/expected.js +++ b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/self-closing-html-element/expected.js @@ -1 +1 @@ -babelHelpers.createRawReactElement("foo", null, {}); \ No newline at end of file +babelHelpers.jsx("foo", {}); \ No newline at end of file diff --git a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/shorthand-attributes/expected.js b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/shorthand-attributes/expected.js index d338020048..bff9127add 100644 --- a/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/shorthand-attributes/expected.js +++ b/packages/babel-plugin-transform-react-inline-elements/test/fixtures/inline-elements/shorthand-attributes/expected.js @@ -1,3 +1,3 @@ -babelHelpers.createRawReactElement(Foo, null, babelHelpers.defaultProps(Foo.defaultProps, { +babelHelpers.jsx(Foo, { bar: true -})); \ No newline at end of file +}); \ No newline at end of file