Home > Software engineering >  Babel via webpack v5 does not generate valid ES3 code for IE8 or WebBrowser control
Babel via webpack v5 does not generate valid ES3 code for IE8 or WebBrowser control

Time:10-26

I am trying to use webpack v5.74.0 with babel-loader 8.2.5 to transpile and pack Javascript code that must be compatible with Microsofts WebBrowser control (i.e IE 8 / ES3 standard). Specifically, the WebBrowser control will throw and "Expected identifier" error when encountering JS code where a method name is also a JS keyword, e.g. Set.delete().

Here is a small test case:

project structure

$ ls -l -R --hide=node_modules
.:
total 266
drwxr-xr-x 1 demo.user 197121      0 Oct 21 17:32 dist/
-rw-r--r-- 1 demo.user 197121 265954 Oct 21 16:56 package-lock.json
-rw-r--r-- 1 demo.user 197121    393 Oct 21 18:09 package.json
drwxr-xr-x 1 demo.user 197121      0 Oct 21 16:06 src/
-rw-r--r-- 1 demo.user 197121    917 Oct 21 17:45 webpack.prod.js
-rw-r--r-- 1 demo.user 197121    145 Oct 21 17:16 xxxbabel.config.json

./dist:
total 1
-rw-r--r-- 1 demo.user 197121 94 Oct 21 17:33 main.js

./src:
total 1
-rw-r--r-- 1 demo.user 197121 94 Oct 21 17:46 main.js

package.json:

{
  "name": "webpack-test",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "build": "webpack --config=webpack.prod.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.19.3",
    "@babel/preset-env": "^7.19.4",
    "babel-loader": "^8.2.5",
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0"
  }
}

webpack.prod.js:

const path = require("path");

module.exports = {
  entry: {
    main: "./src/main.js"
  },
  mode: "production",
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "./dist"),
    environment: {
      arrowFunction: false  // prevent top level arrow IIFE on Webpack 5
    },
    clean: true   // clean output dir before each build
  },
  module: {
    rules: [
      {
        test: /\.js$/,  // transpile JS for older browsers
        exclude: /node_modules/,  // don't mess with node_modules
        use: {
          loader: "babel-loader",
          options: {
            "presets": [
              [
                '@babel/preset-env', {
                  targets: {
                    "ie": "8"   // target for IE 8 ES3 for WebBrowser control
                  },
                  debug: true
                }
              ]
            ],  // let's us use latest JS features
            //"plugins": [ "@babel/plugin-transform-member-expression-literals" ]
          }
        }
      }
    ]
  }
}

Input code: src/main.js

Here I use as example the delete method of the Set object, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set

var d = new Set("a", "b", "c");
d["delete"]("a");
console.log("Set has a? " d.has("a"));

Calling webpack using

npm run build

$ npm run build

> [email protected] build
> webpack --config=webpack.prod.js

@babel/preset-env: `DEBUG` option

Using targets:
{
  "ie": "8"
}

Using modules transform: auto

Using plugins:
  proposal-class-static-block { ie }
  proposal-private-property-in-object { ie }
  proposal-class-properties { ie }
  proposal-private-methods { ie }
  proposal-numeric-separator { ie }
  proposal-logical-assignment-operators { ie }
  proposal-nullish-coalescing-operator { ie }
  proposal-optional-chaining { ie }
  proposal-json-strings { ie }
  proposal-optional-catch-binding { ie }
  transform-parameters { ie }
  proposal-async-generator-functions { ie }
  proposal-object-rest-spread { ie }
  transform-dotall-regex { ie }
  proposal-unicode-property-regex { ie }
  transform-named-capturing-groups-regex { ie }
  transform-async-to-generator { ie }
  transform-exponentiation-operator { ie }
  transform-template-literals { ie }
  transform-literals { ie }
  transform-function-name { ie }
  transform-arrow-functions { ie }
  transform-block-scoped-functions { ie < 11 }
  transform-classes { ie }
  transform-object-super { ie }
  transform-shorthand-properties { ie }
  transform-duplicate-keys { ie }
  transform-computed-properties { ie }
  transform-for-of { ie }
  transform-sticky-regex { ie }
  transform-unicode-escapes { ie }
  transform-unicode-regex { ie }
  transform-spread { ie }
  transform-destructuring { ie }
  transform-block-scoping { ie }
  transform-typeof-symbol { ie }
  transform-new-target { ie }
  transform-regenerator { ie }
  transform-member-expression-literals { ie < 9 }
  transform-property-literals { ie < 9 }
  transform-reserved-words { ie < 9 }
  proposal-export-namespace-from { ie }
  syntax-dynamic-import
  syntax-top-level-await

Using polyfills: No polyfills were added, since the `useBuiltIns` option was not set.
asset main.js 94 bytes [compared for emit] [minimized] (name: main)
./src/main.js 90 bytes [built] [code generated]
webpack 5.74.0 compiled successfully in 1940 ms

webpack / babel output: dist/main.js:

!function(){var a=new Set("a","b","c");a.delete("a"),console.log("Set has a? " a.has("a"))}();

Note the call a.delete() which is invalid for IE8. It will result in the above mentioned "Expected identifier" error message.

Excpected output Expected output would be same as input, i.e. the call to the delete method should read a["delete"]().

I have tried to check with babel alone using their REPL page, where the codes is transpiled correctly: https://babel.dev/repl#?browsers=ie 8&build=&builtIns=false&corejs=false&spec=false&loose=true&code_lz=G4QwTgBAJhC8EDsCmB3CBlJAXAFAIhDwBoI8AjY0gYzwEoBuAKCgDookAbbJfQhoA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=script&lineWrap=true&presets=env,stage-2&prettier=false&targets=&version=7.19.6&externalPlugins=&assumptions={}

Therefore I suppose either the configuration file is incorrect or the webpack/babel-loader configuration is not passed properly to babel. I would appreciate any help that resolves this issue.

CodePudding user response:

So I figured out the solution. Apparently it's not a babel issue but the minification that happens afterwards. Terser (the minifier) messes up the code. I could find the correct option to prevent terser from replacing the property access with dot notation, documented here: https://github.com/terser/terser#compress-options

First we need to include the terser plugin in webpack.prod.js:

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

and then append a terser configuration after the 'module' config:

optimization: {
  minimize: true,
  minimizer: [new TerserPlugin({
    terserOptions: {
        compress: { properties: false },
        mangle: false,
    },
  })],
}

Complete webpack.prod.js:

const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  entry: {
    main: "./src/main.js"
  },
  mode: "production",
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "./dist"),
    environment: {
      arrowFunction: false  // prevent top level arrow IIFE on Webpack 5
    },
    clean: true   // clean output dir before each build
  },
  module: {
    rules: [
      {
        test: /\.js$/,  // transpile JS for older browsers
        exclude: /node_modules/,  // don't mess with node_modules
        use: {
          loader: "babel-loader",
          options: {
            "presets": [
              [
                '@babel/preset-env', {
                  targets: {
                    "ie": "8"   // target for IE 8 ES3 for WebBrowser control
                  },
                  debug: true
                }
              ]
            ],  // let's us use latest JS features
            //"plugins": [ "@babel/plugin-transform-member-expression-literals" ]
          }
        }
      }
    ]
  },
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin({
      terserOptions: {
        compress: { properties: false }, // important: don't rewrite property access using the dot notation, e.g. foo["bar"] → foo.bar
        mangle: false,
      },
    })],
  }
}

Output file dist/main.js is then as expected:

$ cat dist/main.js
!function(){var d=new Set("a","b","c");d["delete"]("a"),console.log("Set has a? " d.has("a"))}();
  • Related