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"))}();