question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

New @babel/preset-react JSX Transform introduces objectSpread helpers, if using the spread operator and targeted to IE 11

See original GitHub issue

Bug 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:closed
  • Created 3 years ago
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
nicolo-ribaudocommented, Oct 22, 2020

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:

  1. Use loose mode for object-rest-spread (1681 bytes)
{
  "presets": [
    ["@babel/preset-env", {
      "exclude": ["proposal-object-rest-spread"]
    }],
    ["@babel/preset-react", {
      "runtime": "automatic"
    }]
  ],
  "plugins": [
      ["@babel/plugin-proposal-object-rest-spread", {
          "loose": true
      }]
  ]
}
  1. If you already have an Object.assign polyfills, you can trim it down to 1367 bytes:
{
  "presets": [
    ["@babel/preset-env", {
      "exclude": ["proposal-object-rest-spread"]
    }],
    ["@babel/preset-react", {
      "runtime": "automatic"
    }]
  ],
  "plugins": [
      ["@babel/plugin-proposal-object-rest-spread", {
          "loose": true,
          "useBuiltIns": true
      }]
  ]
}

In any case, I suggest also using @babel/plugin-transform-runtime (if you have multiple files) to deduplicate Babel helpers across files.

1reaction
chrisapplegatecommented, Oct 22, 2020

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!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Using spread operator on Sets fails to import required Array ...
Both Array.isArray and Array.from used in @babel/helpers should be polyfilled when targeting IE 8.
Read more >
Introducing the New JSX Transform – React Blog
This is why we worked with Babel to offer a new, rewritten version of the JSX transform for people who would like to...
Read more >
7.7.0 Released: Error recovery and TypeScript 3.7 - Babel.js
Today we are releasing Babel 7.7.0! This release includes new parser features like top-level await ( await x() , Stage 3) and Flow...
Read more >
Installing the ES2018 object spread operator babel plugin error
If targeting any IE, make sure to include polyfill for Object.assign. Please use following configuration in your build/vue-loader.conf.js ...
Read more >
@babel/plugin-transform-typeof-symbol - Package Manager
babel72.2mMIT7.18.9. This transformer wraps all typeof expressions with a method that replicates native behaviour. (ie. returning “symbol” for symbols).
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found