oligofren
oligofren

Reputation: 22923

Babel not transpiling imported node_modules to ES5 - includes ES2015 syntax

My babel+webpack config works fine, but the resulting bundle isn't runnable in IE11 as it contains const declarations. I thought having the es2015 preset was enough to fix this? Running $(npm bin)/babel test/some-es2015.js produces strict ES5.1 code, so Babel seems to work, but the actual code that borks in IE11 is in modules imported from node_modules.

When grepping for 'const ' in my resulting bundle I get certain lines like this (the eval is due to eval source mapping btw):

eval("\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst validator = __webpack_require__(/*! validator */ \"./node_modules/tcomb-additional-types/node_modules/validator/index.js\");\nconst t = __webpack_require__(/*! tcomb */ \"./node_modules/tcomb/index.js\");\nconst IP = t.refinement(t.String, validator.isIP);\nexports.IP = IP;\nexports.default = IP;\n//# sourceMappingURL=ip.js.map\n\n//# sourceURL=webpack:///./node_modules/tcomb-additional-types/lib/string/ip.js?");

The important part to note is the stuff such as const validator =. This isn't ES5.1 syntax. My own code seems to have been transpiled to ES5 just fine. I can see this file in /node_modules/tcomb-additional-types/lib/string/ip.js, where they use const, so this isn't Babel adding consts, but the source containing them. Most of the other packages are ES5.

So far, I have found that most consts are from material-ui and tcomb-additional-types.

Babel .babelrc:

{
    "compact": false,
    "presets": [
        "es2015",
        "es2017"
    ],
    "plugins": [
        ["transform-runtime", {
            "polyfill": false,
            "regenerator": true
        }],
        "transform-class-properties",
        "transform-react-jsx",
        "transform-object-rest-spread"
    ]
}

Webpack config:

const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');

/** @returns {String} an absolute path */
function toRoot(rootRelativeDir) {
  return path.resolve(__dirname, '..', rootRelativeDir);
}

module.exports = {
  entry: ['./src/app.js', './styles/flex.less'].map(toRoot),
  output: {
    filename: 'bundle.js',
    path: toRoot('.webpack/dist')
  },
  resolve: {
    extensions: ['.js', '.jsx'],
    alias: {}
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              /* General options are read using .babelrc - only webpack loader specific here */
              cacheDirectory: toRoot('.webpack/babel_cache')
            }
          }
        ]
      }
    ]
  },
  plugins: [new CopyWebpackPlugin([toRoot('public')])]
};

Upvotes: 4

Views: 7788

Answers (3)

oligofren
oligofren

Reputation: 22923

My underlying problem was that some Node packages are not written using ES5 syntax, and the Babel transforms did not transform them for some reason. This is a normal issue

Finding why this happened was pretty easy (@Vincent's answer helped); I had exclude: /node_modules/ in the config. Of course, removing this would "fix" the issue, but it would introduce new issues, as the exclude is there for a reason, as you don't want Babel to process every file in there.

So what you want is this: selective filtering allowing some modules.

Trying to construct a regex that will allow a list of packages under node_modules, but restrict the rest is cumbersome and error prone. Thankfully the Webpack docs describe that the condition rules, of which exclude is one, can be

  • A string: To match the input must start with the provided string. I. e. an absolute directory path, or absolute path to the file.
  • A RegExp: It's tested with the input.
  • A function: It's called with the input and must return a truthy value to match.
  • An array of Conditions: At least one of the Conditions must match.
  • An object: All properties must match. Each property has a defined behavior.

Creating such a function is easy! So instead of having exclude: /node_modules, I changed it to be exclude: excludeCondition, where excludeCondition is the following function:

function excludeCondition(path){

  const nonEs5SyntaxPackages = [
    'material-ui',
    'tcomb-additional-types'
  ]

  // DO transpile these packages
  if (nonEs5SyntaxPackages.some( pkg => path.match(pkg))) {
    return false;
  }

  // Ignore all other modules that are in node_modules
  if (path.match(toRoot("node_modules"))) { return true; }

  else return false;
}

This fixed my issue, as there is just a tiny number of packages using ES2015 syntax, so adding them to the allowlist is manageable.


Addendum Since people ask about the toRoot(), this is the verbatim code:

/** @returns {String} an absolute path */
function toRoot(rootRelativeDir) {
  return path.resolve(__dirname, '..', rootRelativeDir);
}

Adapt to your own needs.

The fuller code:

const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');

/** @returns {String} an absolute path */
function toRoot(rootRelativeDir) {
  return path.resolve(__dirname, '..', rootRelativeDir);
}

function excludeCondition(path) {
  const nonEs2015Packages = ['tcomb-additional-types', 'material-ui'];

  // DO transpile these packages
  if (nonEs2015Packages.some(pkg => path.match(pkg))) {
    return false;
  }

  // Ignore all other modules that are in node_modules
  return Boolean(path.match(toRoot('node_modules')));
}

module.exports = {
  entry: ['./src/app.js', './styles/custom.less', './styles/flex.less', './styles/rc_slider.less'].map(toRoot),
  output: {
    filename: 'bundle.js',
    path: toRoot('.webpack/dist')
  },
  resolve: {
    extensions: ['.js', '.jsx'],
    alias: {}
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: excludeCondition,
        use: [
          {
            loader: 'babel-loader',
            options: {
              /* General options are read using .babelrc - only webpack loader specific here */
              cacheDirectory: toRoot('.webpack/babel_cache')
            }
          }
        ]
      }
    ]
  },
  plugins: [new CopyWebpackPlugin([toRoot('public')])]
};

Upvotes: 5

David Vodrážka
David Vodrážka

Reputation: 63

I had a similar problem and I fixed it by renaming .babelrc.js to babel.config.js.

Apparently, .babelrc has smaller scope than babel.config.js, if you'd like to read more about that, check out this post:

When to use babel.config.js and .babelrc

Upvotes: 3

Vincent Rolea
Vincent Rolea

Reputation: 1663

The same problem happened to me as well. Some node modules don't provide browser support and target node versions that leverage newer ES syntax.

I came across that handy package that transpiles node modules code:

https://www.npmjs.com/package/babel-engine-plugin

It solved my problem regarding IE11 support, hope it helps

Upvotes: 1

Related Questions