3

PS: It's the first time I've given up to the point of composing my first question here. Would be really grateful if someone could help. Thanks!

I'm trying to load my ReactJS web app in iOS 10 Safari on a relatively old iPad (I use webpack and babel-loader, and serve it using webpack-dev-server).

I'm getting the following Syntax error:

SyntaxError: Unexpected token '...'. Expected a property name.

(The page loads fine on all devices/browsers I've tried so far.)

The error is caused by this line of transpiled code:

eval("\nconst publicIp = __webpack_require__(/*! public-ip */ \"./node_modules/public-ip/browser.js\");\n\nconst isOnline = async options => {\n\toptions = {\n\t\ttimeout: 5000,\n\t\tversion: 'v4',\n\t\t...options\n\t};\n\n\ttry {\n\t\tawait publicIp[options.version](options);\n\t\treturn true;\n\t} catch (_) {\n\t\treturn false;\n\t}\n};\n\nmodule.exports = isOnline;\n// TODO: Remove this for the next major release\nmodule.exports.default = isOnline;\n\n\n//# sourceURL=webpack:///./node_modules/is-online/browser.js?");

where we can observe as seen in the source https://github.com/sindresorhus/is-online/blob/master/browser.js:

const isOnline = async options => {
    options = {
        timeout: 5000,
        version: 'v4',
        ...options
    };

    // ...
};

Looks to me like object destructuring using the ... spread operator is not supported. The code is from a npm module I'm using called "is-online".

I tried adding the "@babel/plugin-transform-destructuring" plugin to .babelrc to see if it could solve this. Everything compiled but this part of code was identical, so it still produced the same error.

I found this Twitter conversation describes the same problem and also with Safari, yet he managed to solve it because he "needed to also activate the transform plugin for it: transform-object-rest-spread":

https://twitter.com/beberlei/status/984083670012256258

So I tried it and it didn't work.

Then I stepped up my plugins game in .babelrc and after searching for similar cases online, trying different configs, updating babel using npx babel-upgrade, deleting and reinstalling node_modules and putting the plugins direclty into module.rules[0].options.plugins I gave up on it with:

// .babelrc

{
    "presets": [
        "@babel/preset-env",
        "@babel/preset-react"
    ],
    "plugins": [
        "@babel/plugin-transform-spread",
        "@babel/plugin-transform-destructuring",
        "@babel/plugin-transform-parameters",
        "@babel/plugin-proposal-object-rest-spread",
    ]
}

...but it still gives the error. It also tried to put "@babel/plugin-transform-runtime" in there: same.

My webpack config as of right now:

// webpack.dev.js

const path = require("path");
const webpack = require("webpack");

const TerserPlugin = require('terser-webpack-plugin');

module.exports = [

    // App

    {

        mode: 'development',

        entry: {
            app: "./src/index.js"
        },

        module: {
            rules: [
                {
                    test: /\.(js|jsx)$/,
                    exclude: /(node_modules|bower_components)/,
                    loader: "babel-loader",
                    options: {
                        presets: ["@babel/env"],
                    }
                },
                {
                    test: /\.css$/,
                    use: ["style-loader", "css-loader", "postcss-loader"]
                },
                {
                    test: /\.(png|svg|jpg|gif)$/,
                    use: [
                        'file-loader'
                    ]
                }
            ]
        },

        resolve: { extensions: ["*", ".js", ".jsx"] },

        output: {
            filename: "app-v0.9.6.js",
            path: path.resolve(__dirname, "public/dist/"),
            publicPath: "/dist/"
        },

        plugins: [new webpack.HotModuleReplacementPlugin()],

        devServer: {
            host: '0.0.0.0',
            disableHostCheck: true,
            port: 80,
            contentBase: path.join(__dirname, "public/"),
            publicPath: "http://localhost:3000/dist/",
            hotOnly: true
        },

// Fixes Safari 10-11 bugs
// Has nothing to do with this question: already tried to comment this out
        optimization: {
            minimizer: [new TerserPlugin({
                terserOptions: {
                    safari10: true,
                },
            })],
        },
    },

    // Library

    {

        mode: 'development',

       // ... 
       // another output that's exposed as a global variable (library)

    }
];

Here are the dev dependencies:

// package.json

...

  "devDependencies": {
    "@babel/cli": "^7.5.5",
    "@babel/core": "^7.5.5",
    "@babel/plugin-proposal-object-rest-spread": "^7.0.0",
    "@babel/plugin-transform-destructuring": "^7.0.0",
    "@babel/plugin-transform-parameters": "^7.0.0",
    "@babel/plugin-transform-runtime": "^7.5.5",
    "@babel/plugin-transform-spread": "^7.0.0",
    "@babel/preset-env": "^7.0.0",
    "@babel/preset-react": "^7.0.0",
    "babel-loader": "^8.0.6",
    "babel-preset-env": "^1.7.0",
    "css-loader": "^3.2.0",
    "file-loader": "^4.2.0",
    "html-webpack-plugin": "^3.2.0",
    "postcss-loader": "^3.0.0",
    "style-loader": "^0.23.1",
    "webpack": "^4.39.1",
    "webpack-cli": "^3.3.6",
    "webpack-dev-server": "^3.8.0",
    "webpack-merge": "^4.2.1"
  },

...

If someone knows how to configure it correctly I'd be very grateful.

andreyb
  • 41
  • 7

2 Answers2

1

So as you understood, uses of the “ES2018 spread syntax in object literal” (aka ellipsis) from a module you imported are leaking into your transpiled bundle, thereby upsetting the most pitiful browsers. Which happens because:

Webpack is set to ignore your node_modules, so is-online does not get transpiled.

Here's a practical solution for you -- modify the exclude rule of your webpack.config.js as follows:

exclude: /(node_modules\/(?!is-online)|bower_components)/,

Aka. “exclude bower_components, and all node_modules except for is-online”

Just in case -- to exclude more modules, write:

exclude: /(node_modules\/(?!is-online|another-es6-module|yet-another-one)|bower_components)/,

There may be more pretty ways to put it; if you're an aesthete, try your luck here.

Once done, check the output file for the string "...". Hopefully it's gone!

If you feel fancy, you could automate that checking the output for "..." after each build so as to be sure not to miss any module. But then again, it would fire on legit string literals containing ellipses...

Phew! I almost thought other forces were at play...


Old response due to error in question:

Your fat-arrow function returns an object literal that’s not wrapped in parentheses.

Mozilla says:

Returning object literals

Keep in mind that returning object literals using the concise body syntax params => {object:literal} will not work as expected.

var func = () => { foo: 1 };
// Calling func() returns undefined!

var func = () => { foo: function() {} };
// SyntaxError: function statement requires a name

This is because the code inside braces ({}) is parsed as a sequence of statements (i.e. foo is treated like a label, not a key in an object literal).

You must wrap the object literal in parentheses:

var func = () => ({ foo: 1 });
hugo
  • 3,067
  • 2
  • 12
  • 22
  • Hi hugo! Thank you for answering with your explanation. I'm sorry I noticed that I made a mistake in the part where I showed the "unwrapped" version of the transpiled code, please have a look at the updated part if you could. Do you think it is still the same issue here? EDIT: the code is not mine, I never usually use such convoluted syntax.. Any way to configure Babel to cope with this? – andreyb Aug 10 '19 at 18:39
  • You said "where we can observe `options = {\n\t\ttimeout: 5000,\n\t\tversion: 'v4',\n\t\t...options\n\t}`". What I see is it's written `(blahblahblah)const isOnline = async options => {\n\t\ttimeout: 5000,\n\t\tversion: 'v4',\n\t\t...options\n\t}(blahblahblah)`. Is this correct? – hugo Aug 10 '19 at 18:42
  • I found the source: https://github.com/sindresorhus/is-online/blob/master/browser.js – andreyb Aug 10 '19 at 18:46
  • Yes the source is correct, but not your `eval("...")` code. Do you know what produces it? – hugo Aug 10 '19 at 18:54
  • It's a statement in `function(module, exports, __webpack_require__) { //here is the line }` function, so webpack. Tbh I don't understand what you mean by the source is correct but not the eval. I tried again and it the same. – andreyb Aug 10 '19 at 19:08
  • I'm saying the transpiled code contains the error I pointed out, but not the source before transpiling... they are not equivalent afaict – hugo Aug 10 '19 at 19:09
  • In your explanation you're pointing that the function is returning an object, but it just really returns a boolean in the `try {} catch () {}` block.. Am I missing something? This piece of code runs fine in every browser, I just checked that it's identical in my Mac's OS X Safari. To me it looks like the problem has to do with `...`, don't you think? – andreyb Aug 10 '19 at 19:23
  • You're right, I was looking at `const isOnline = async options => {\n\toptions = {\n\t\ttimeout: 5000,\n\t\tversion: 'v4',\n\t\t...options\n\t};` and I was confused by the `options` inside `options`. It should work indeed -- the codes are equivalent, which is reassuring, but after your edit my answer no longer stands... – hugo Aug 11 '19 at 00:37
  • 1
    Ok, I think I found the issue, I updated my answer :) – hugo Aug 11 '19 at 02:47
  • thank you so much! your answer has indeed helped me figure it out. I'm posting my steps to the solution :) – andreyb Aug 11 '19 at 12:42
0

Fixed it in many ways thanks to @hugo.

The original problem was that the module itself wasn’t processed by babel-loader.

As @hugo suggested (adding “is-online” to the exclude list), my exclude became like so:

exclude: /(node_modules\/(?!is-online|public-ip)|bower_components)/

Then I started getting another error:

enter image description here

After looking for a solution, I figured I also had to add import "regenerator-runtime/runtime”; in my code before I import that “is-online” module, as suggested here: https://stackoverflow.com/a/56754212/11033276.

.babelrc (with no plugins)

{
    "presets": [
        "@babel/preset-env",
        "@babel/preset-react", 
    ],
}

I’m still unsure whether I have to also add useBuiltIns: entry to @babel/preset-env as it already seems to be working this way. I’m fairly new to web dev so if someone could suggest an optimization it would be nice!

Lastly, I uninstalled a bunch of dependencies I had tried attempting to fix this.

package.json

// …
  "devDependencies": {
    "@babel/cli": "^7.5.5",
    "@babel/core": "^7.5.5",
    "@babel/preset-env": "^7.5.5",
    "@babel/preset-react": "^7.0.0",
    "babel-loader": "^8.0.6",
    "css-loader": "^3.2.0",
    "file-loader": "^4.2.0",
    "html-webpack-plugin": "^3.2.0",
    "postcss-loader": "^3.0.0",
    "style-loader": "^0.23.1",
    "webpack": "^4.39.1",
    "webpack-cli": "^3.3.6",
    "webpack-dev-server": "^3.8.0",
    "webpack-merge": "^4.2.1"
  },
// …

I doubled-checked it all still works by reinstalling node_modules.

webpack.dev.js

// ...
                {
                    test: /\.(js|jsx)$/,
                    exclude: /(node_modules\/(?!is-online)\/(?!public-ip)|bower_components)/,
                    loader: "babel-loader",
                    options: {
                        presets: ["@babel/env"],
                    }
                },
// …
hugo
  • 3,067
  • 2
  • 12
  • 22
andreyb
  • 41
  • 7
  • Glad you solved it! I think your regex `/(node_modules\/(?!is-online)\/(?!public-ip)|bower_components)/` [(test 1)](https://regex101.com/r/TXWZv8/2/) is wrong and you meant something like `/(node_modules\/(?!(is-online|public-ip))|bower_components)/` [(test 2)](https://regex101.com/r/TXWZv8/3) – hugo Aug 11 '19 at 13:19