preact-cli/babel/typescript - getting Symbol.iterator and [...spread] to work correctly

416 views Asked by At

In my code, I have a function called range that produces ranges. The implementation is as follows:

export const range = (min: number, max: number) => {
    // ...

    return {
        // ...
        *[Symbol.iterator]() {
            let n = min

            while (n <= max) yield n++
        }
    }
}

Array.from(range(1, 5)) gives [1, 2, 3, 4, 5], as you'd expect. However, [...range(1, 5)] gives [{[Symbol.iterator]: ƒ}] (array of length 1, containing the range object), which is clearly not correct. If I attach range to the window object and call it from the browser console, [...range(1, 5)] gives [1, 2, 3, 4, 5], as expected.

Upon further debugging, it seems the spread operator is being transpiled to this:

// console.log(() => [...range(1, 5)])
ƒ () { return [].concat(range(1, 5)) }

This would work OK if what was being spread was an array, but fails for other types of iterables.

.tsconfig is identical to ones I've used before, targeting ESNext, and changing downlevelIteration to either true or false does nothing, so I'm fairly confident the problem isn't there.

It looks like this is something to do with Babel, but I can't work out how to configure it correctly. Legacy browser support isn't much of a concern - if it's working on latest Chromium and Firefox, I'm happy.

package.json:

"browserslist": "last 3 chrome versions, last 3 firefox versions"

.babelrc:

{ "presets": [ [ "preact-cli/babel", { "modules": "commonjs" } ] ] }

preact.config.js is identical to the one here: preact.config.js permalink. Here's the relevant part:

webpack(config, env, helpers, options) {
    // ...

    config.module.rules = config.module.rules.map(rule => {
        if (rule.loader === 'babel-loader') {
            const use = [
                {
                    loader: 'babel-loader',
                    options: rule.options
                },
                {
                    loader: 'ts-loader'
                }
            ]

            return {
                ...rule,
                loader: undefined,
                options: undefined,
                use,
            }
        }
        // ...    
    })
    // ...
}

How would I go about fixing this?

2

There are 2 answers

2
Lionel Rowe On

Finally fixed this by simply removing babel-loader altogether. I replaced the relevant part of preact.config.js with this:

if (rule.loader === 'babel-loader') {
    return { loader: 'ts-loader' }
}

I'd still be curious to see answers for how to fix this without removing Babel entirely, though.

0
Alexandr Ossip On

I will just repeat solution provided by @PeterLehnhardt in comments, so everybody can see it: Create preact.config.js

export default {
  webpack(config, env, helpers, options) {
    const { rule } = helpers.getLoadersByName(config, 'babel-loader')[0];
    const babelConfig = rule.options;

    babelConfig.assumptions = {
      iterableIsArray: false, 
    }
  }
}