New @babel/preset-react JSX Transform introduces objectSpread helpers, if using the spread operator and targeted to IE 11
See original GitHub issueBug Report
- I would like to work on a fix!
Current behavior
I was trying out the new JSX transform in React 16.14, by setting @babel/preset-react
to runtime automatic
. The blurb had said a possible reduction in code size, but I actually found my minified production bundle went up about 10% in size (300k -> 330k).
After some investigation, I think it’s because I use the spread operator a lot in my React components, many of which are HOCs or wrappers, and the new JSX transform produces much more verbose output to transform an object spread when the spread properties are then input as the props of a child React element.
Input Code
I can’t share my original code but can reproduce it with the simplified example in this repo: https://github.com/chrisapplegate/babel-react-experiment
The input consists of a very simple file, as below.
import React from "react";
export default function MyComponent(props) {
const { children, ...otherProps} = props;
return <div {...otherProps}>
{children}
</div>
}
Then when I run npx babel index.jsx
, I am inspecting the output produced.
Expected behavior
When the runtime
option in .babelrc
for @babel/preset-react
is set to "classic"
, Babel produces 22 lines and 1290 bytes of processed JavaScript:
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = MyComponent;
var _react = _interopRequireDefault(require("react"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
function MyComponent(props) {
var children = props.children,
otherProps = _objectWithoutProperties(props, ["children"]);
return /*#__PURE__*/_react["default"].createElement("div", otherProps, children);
}
When the runtime
is set to "automatic"
to use the New JSX Transform, this balloons to 2476 bytes, nearly double the size:
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = MyComponent;
var _jsxRuntime = require("react/jsx-runtime");
var _react = _interopRequireDefault(require("react"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
function MyComponent(props) {
var children = props.children,
otherProps = _objectWithoutProperties(props, ["children"]);
return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", _objectSpread(_objectSpread({}, otherProps), {}, {
children: children
}));
}
I would have thought the sizes would be roughly comparable, but it appears the New JSX Transform has a much more verbose conversion of the spread operator.
Babel Configuration
- Filename:
.babelrc
{
"presets": [
[
"@babel/preset-env"
],
[
"@babel/preset-react",
{
"runtime": "automatic" // Set to "classic" to get original behavior back
}
]
]
}
Environment
System:
OS: macOS 10.15.7
Binaries:
Node: 14.9.0 - /usr/local/bin/node
npm: 6.14.8 - /usr/local/bin/npm
npmPackages:
@babel/cli: ^7.12.1 => 7.12.1
@babel/core: ^7.12.3 => 7.12.3
@babel/preset-env: ^7.12.1 => 7.12.1
@babel/preset-react: ^7.12.1 => 7.12.1
Possible Solution
I don’t have any solutions for this bug - I am not sure even if it is a bug or an expected feature/side effect (in which case, if there is a way to reduce the impact, it would be very much appreciated).
Issue Analytics
- State:
- Created 3 years ago
- Comments:6 (3 by maintainers)
The main problem is that React’s new helpers expect
children
to be passed alongside with the props, so we have to merge the two objects somehow.I cloned your repository, and managed to get a few size reductions:
Object.assign
polyfills, you can trim it down to 1367 bytes:In any case, I suggest also using
@babel/plugin-transform-runtime
(if you have multiple files) to deduplicate Babel helpers across files.Understood - by explicitly excluding IE11 in the browser query, I get the file size down to something much more acceptable now. I’ll weigh up the pros & cons on whether to exclude it now, or stick with classic.
As I don’t think it’s worth your time squeezing down the bytes just for IE 11, I’ll close the issue. Thanks for the swift help!