Cloning Set using spread operator results in nested set in Gatsby

Viewed 163

I noticed inconsistent behavior of object of type Set in a regular javascript app and in Gatsby app.

In codesandbox.io, create a regular React app and put these lines somewhere at the beginning:

const set = new Set(["A", "B", "C"])
const set2 = new Set([...set, "D"])
console.log(set2)

The console prints:

Set {}
    0: "A"
    1: "B"
    2: "C"
    3: "D"

Then, create a new Gatsby app and put the same code. The console will print:

Set {}
    0: Set
        0: "A"
        1: "B"
        2: "C"
    1: "D"

Why are these results different and how to make the Set in a Gatsby app behave like a regular Set (correctly clone with spread operator)?

1 Answers

Wow, nice find! I love easily reproducible questions like this.

The problem stem from Gatsby's use of babel (source code) in loose mode. The TL;DR on loose mode is that it may generate faster code, but in exchange, less es6 compatible.

If you go to Babel repl and turn on es2015-loose on the left sidebar, you'll see that your code will be transformed like this:

//original
const set = new Set(['a', 'b', 'c'])
const set2 = new Set([ ...set, 'd'])

//transformed
var set = new Set(['a', 'b', 'c']);
var set2 = new Set([].concat(set, ['d']));

You can see the problem here. [].concat(new Set(['a'])) will not turn set into an array, hence why we ended up with a Set of [Set, 'd'].


Fix this

The easy route is to work around this in your code:

  const set = new Set(['a', 'b', 'c'])
  const set2 = new Set([ ...Array.from(set), 'd'])
  // Set[ 'a', 'b', 'c', 'd' ]

Or you can provide Gatsby with your own babel config, by creating a .babelrc at the root per usual:

touch .babelrc

# install new dependencies -- 
# they're probably already installed, 
# but I think it's better to be explicit
yarn add @babel/preset-env @babel/preset-react @babel/plugin-proposal-class-properties babel-plugin-macros @babel/plugin-syntax-dynamic-import @babel/plugin-transform-runtime -D

And copy gatsby's default config over, but with loose mode turn off:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "corejs": 2,
        "loose": false,
        "modules": false,
        "useBuiltIns": "usage",
        "targets": "> 0.25%, not dead"
      }
    ],
    [
      "@babel/preset-react",
      {
        "useBuiltIns": true,
        "pragma": "React.createElement",
        "development": true
      }
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-proposal-class-properties",
      {
        "loose": true
      }
    ],
    "babel-plugin-macros",
    "@babel/plugin-syntax-dynamic-import",
    [
      "@babel/plugin-transform-runtime",
      {
        "helpers": true,
        "regenerator": true
      }
    ]
  ]
}
Related