Webpack-encore: How to uglify bootstrap (ES6)

Created on 15 Aug 2017  路  17Comments  路  Source: symfony/webpack-encore

I am trying to use bootstrap partly

global.Tether = require('tether');
global.Popper = require('popper.js/dist/umd/popper');
require('bootstrap/js/src/modal');

, but I alway get

test.js from UglifyJs
Unexpected token: operator (>) [test.js:1722,20]

in production mode. Dev mode is fine.

I added the following to activate ES6:

package.json

"devDependencies": {
  ...  
  "uglify-js": "https://github.com/mishoo/UglifyJS2#harmony-v2.8.22",
  "uglifyjs-webpack-plugin": "^0.4.6"
}

webpack.config.js

const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
...
.addPlugin(new webpack.ProvidePlugin(new UglifyJSPlugin()))

But there is no effecft. Still the same error. Any ideas?

Most helpful comment

Hi @wenmingtang,

Are you trying to transpile your code with ES6 as a target or are you requiring files from a vendor that are written using ES6?

In the first case you can try using the 1.0.0-beta version of uglifyjs-plugin:

  • add it to your project using yarn: yarn add --dev uglifyjs-webpack-plugin@^1.0.0-beta
  • replace the version from webpack by that one in your webpack.config.js:
const Encore = require('@symfony/webpack-encore');
const webpack = require('webpack');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

// Configure Encore
Encore
    .setOutputPath('build/')
    .setPublicPath('/')
    // (...)
;

const webpackConfig = Encore.getWebpackConfig();

// Remove the old version first
webpackConfig.plugins = webpackConfig.plugins.filter(
    plugin => !(plugin instanceof webpack.optimize.UglifyJsPlugin)
);

// Add the new one
webpackConfig.plugins.push(new UglifyJsPlugin());

// export the final configuration
module.exports = webpackConfig;

In the other case try looking for an already transpiled version of the files you are requiring. If you can't find one the best way right now would probably be to manually modify the exclude option of the babel-loader as stated above.

All 17 comments

did you tried to call:

Encore.autoProvideVariables({
    'window.Tether': 'tether'
})

in your case should it be: 'global.Tether': 'tether' maybe?

I had troubles before with bootstrap4 regarding not finding names (Tether), solution was auto provide the variables.

Unfortunately that is not helping. But thank you for the hint.

The error Unexpected token: operator (>) [test.js:1722,20] appears in the shell, if I run ./node_modules/.bin/encore production not in the javascript console.

Tether isn't required by Bootstrap v4-beta anymore

Hi @Sozialarchiv,

After a quick test it looks like this issue isn't present if you require bootstrap/js/dist/modal instead of bootstrap/js/src/modal.

You also don't need to add your own uglify-js:

$ yarn why uglify-js
yarn why v0.27.5
[1/4] Why do we have the module "uglify-js"...?
[2/4] Initialising dependency graph...
[3/4] Finding dependency...
[4/4] Calculating file sizes...
info This module exists because "@symfony/webpack-encore#webpack#uglifyjs-webpack-plugin" depends on it.

So, looking back at it a bit more, the version of Webpack shipped with Encore currently installs uglifyjs-webpack-plugin v0.4.6 which uses uglify-js v2.8.29. This version of uglify-js doesn't support ES6 and, since vendors don't go through Babel it fails if you require something from them written using ES6. It works using the files in dist since they have already been processed by Babel using the ES2015 preset.

I guess you could do what you were initially trying to and modify the uglify-js part in order to use a more recent version such as uglify-es (you'll probably need to remove the plugin added by Encore before adding yours though) but be aware than the resulting file may include things that are not supported by some browsers.

If you really want to import the src files you could modify the js rule added by Encore in order to remove the exclude: /(node_modules|bower_components)/:

const config = Encore.getWebpackConfig();

for (const rule of config.module.rules) {
    if (rule.use) {
        for (loader of rule.use) {
            if (loader.loader === 'babel-loader') {
                delete rule.exclude;
            }
        }
    }
}

module.exports = config;

Since Babel will then use the .babelrc file from Bootstrap you'll also need to yarn add --dev babel-preset-es2015 transform-es2015-modules-strip.

Edit: Be aware that this last method will slow down your builds and that if another module also uses a .babelrc file you'll also need to add the presets/plugins it uses (or disable .babelrc files). In my opinion you should use the dist files whenever it's possible to do so, and when it isn't slightly modify the exclude rule to add an exception instead of removing it entirely.

I'm very eager to fix the uglify issue. I'm not sure yet if they have a stable version for ES6 or if we can support both. I haven't looked into it for a few weeks. But to me, the fact that our Uglify chokes on ES6 is a bug.

Also, about Babel not going through vendors, I also think that's a problem. It makes sense in most cases, but not everywhere. We need a way to configure this. @Lyrkan wasn't there at least one other issue about this? I was on holiday last week, so I'm still "logging back in" mentally :)

After a quick test it looks like this issue isn't present if you require bootstrap/js/dist/modal instead of bootstrap/js/src/modal.

You are right. That is also the recommondaton of the bootstrap website. I can test that tomorrow.

Thanks a lot for your great help. symfony webpack-encore is really amazing.

@weaverryan There is uglify-es (the harmony branch of uglify-js) that works with ES6, but its API is not backward compatible with uglify-js v2 (currently used by uglifyjs-webpack-plugin). It looks like there has been some work done to integrate that one into uglifyjs-webpack-plugin but that's quite recent and still in beta.

I didn't see any other issue related to Babel not processing vendors. It isn't that weird if you think about it: if a library supports ES5 then it will probably include a dist version that has already been transpiled. But as you said there may be some cases in which that would be needed. For these ones, modifying the exclude regex using negative lookahead (e.g.: /node_modules\/(?!bootstrap)/) or a function should do the trick but currently there is no way to change it without diving into the generated Webpack configuration.

There is one thing I'm wondering though: let's say we start using uglify-es and provide a way to modify the exclude. If an user requires something written using ES6 (e.g. require('bootstrap/js/src/modal')) it will not fail anymore, but the code will still not be transpiled unless the exclude has been configured. That may lead to other issues, how could we warn the user in this case?

It looks like there has been some work done to integrate that one into uglifyjs-webpack-plugin but that's quite recent and still in beta.

Ah, very good! So that gives us a good roadmap: when 1.0.0 comes out of uglifyjs-webpack-plugin and when webpack starts using it (https://github.com/webpack/webpack/blob/f75418f6683af5e7cab86d67d896bf5001075836/package.json#L25), then we'll naturally start using it.

If an user requires something written using ES6 (e.g. require('bootstrap/js/src/modal')) it will not fail anymore, but the code will still not be transpiled unless the exclude has been configured.

Very good point! The current, non-es6 version of Uglify is sort of "protecting" the user currently :). I'll have to think about this - I don't see a simple way currently. And it is valid for the user to choose not to transpile back to ES6. We transpile to ES6 for KnpU... but honestly, all our users use new browsers, so we don't really need to (yay for our users!). So I don't have an answer yet, but we'll use this issue to track.

With the js/dist links I could solve the bootstrap problem. Should I close the issue or should I let it open for the ES6 discussion.

Thanks a lot for your help.

Awesome! Leave it open - we'll keep tracking the underlying issue :)

Same problem.

馃憥

Hi @wenmingtang,

Are you trying to transpile your code with ES6 as a target or are you requiring files from a vendor that are written using ES6?

In the first case you can try using the 1.0.0-beta version of uglifyjs-plugin:

  • add it to your project using yarn: yarn add --dev uglifyjs-webpack-plugin@^1.0.0-beta
  • replace the version from webpack by that one in your webpack.config.js:
const Encore = require('@symfony/webpack-encore');
const webpack = require('webpack');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

// Configure Encore
Encore
    .setOutputPath('build/')
    .setPublicPath('/')
    // (...)
;

const webpackConfig = Encore.getWebpackConfig();

// Remove the old version first
webpackConfig.plugins = webpackConfig.plugins.filter(
    plugin => !(plugin instanceof webpack.optimize.UglifyJsPlugin)
);

// Add the new one
webpackConfig.plugins.push(new UglifyJsPlugin());

// export the final configuration
module.exports = webpackConfig;

In the other case try looking for an already transpiled version of the files you are requiring. If you can't find one the best way right now would probably be to manually modify the exclude option of the babel-loader as stated above.

@Lyrkan
OK, Problem solved.
Thanks.

It looks like UglifyJsPlugin v1.0.0 (using uglify-es) is now scheduled for webpack v4.0.0

I can confirm that this still an issue, I encountered this problem when uglifyjs wouldn't minify my vuejs component, replacing webpack.optimize.UglifyJsPlugin with uglifyjs-webpack-plugin as @Lyrkan suggested made it work :+1:

This shouldn't be an issue anymore with the latest version of Encore (0.21.0):

  • It uses terser-webpack-plugin (which supports ES6+) instead of uglifyjs-webpack-plugin
  • There is a way to change the files that are excluded from Babel's preprocessing by calling configureBabel()
Was this page helpful?
0 / 5 - 0 ratings