Next.js: Make minify(uglify) opt-in or use a substitute

Created on 18 Feb 2017  路  20Comments  路  Source: vercel/next.js

EDIT:

Or we could use some other ways to uglify.

Currently, magnification takes a lot of time when we've many pages.
@rauchg removed it from our https://zeit.co and we got 5x faster next build.

Also, newer CDNs support on the fly minify and gzipping. So, this won't be a big problem.
So, here's the plan

  • [ ] - We remove uglifyjs
  • [ ] - Add a sample to how to add it
  • [ ] - Add a wiki on how to enable it on CDN
  • [ ] - Show how to lazy minify/gzip them in production

Most helpful comment

I started to use a substitute of a newer version that allows me to minify ES6.

npm install [email protected] <- check for recent version, for me it was 1.0.0-beta.1

const Uglify = require('uglifyjs-webpack-plugin');

module.exports = {
  webpack: function (c) {
    c.plugins = c.plugins.filter(
      (plugin) => (plugin.constructor.name !== 'UglifyJsPlugin')
    )

    c.plugins.push(
      new Uglify()
    );

    return c;
  }
}

All 20 comments

@arunoda

I feel like uglify should be default, with an opt-out. Part of what makes Next.js great is it's capability of creating production ready apps with no configuration and only a single command: next build.

@eezing good point.
On the other hand it makes next build to take a lot of time.

Code for out-out is possible even at this stage:

module.exports = {
  webpack (cfg) {
    cfg.plugins = cfg.plugins.filter((plugin) => {
      if (plugin.constructor.name === 'UglifyJsPlugin') {
        return false
      } else {
        return true
      }
    })
    return cfg
  },
}

And it's not looking good.

Here is a better snippet (the functionality is the same, but just fewer lines of code):

module.exports = {
  webpack (cfg) {
    cfg.plugins = cfg.plugins.filter(
      (plugin) => (plugin.constructor.name !== 'UglifyJsPlugin')
    )
    return cfg
  },
}

I like uglify being on by default. As @eezing said, a big value prop of next is zero-configuration production-ready builds. The biggest problem I see with uglify is just hunting down the errors it spits out.

Uglify gives a line number and filename when it fails, but the problem is that the raw compiled files that uglify is operating on are not accessible by the user of next build.

What if next.js, in its webpack step before uglify, built the non-minified non-combined files (commons.js / manifest.js / main.js) into a folder like .next/prebuild/, then removed them after the uglify step?

Then on uglify failure, the user could be told to check next/prebuild for the files, and those files would have the same line numbers as the UglifyJS errors.

It's a slightly messy way to do it, but it seems like an easy solution to a very annoying debugging problem.

What if next.js, in its webpack step before uglify, built the non-minified non-combined files (commons.js / manifest.js / main.js) into a folder like .next/prebuild/, then removed them after the uglify step?

@ajoslin That's a good suggestion and that's something I had in mind as well.

  • We minify all our files with babili
  • Then for the app.js we can use uglifyjs or closure for that app.js outside of Webpack.

I have a project that targets only Firefox Nightly. I ran into this when adding a custom .babelrc using babel-preset-modern-browsers. Having an optional build chain for modern browsers would be great.

Maybe not related but did you guys try out https://github.com/gdborton/webpack-parallel-uglify-plugin
Could boost up build time a lot if you are building on a multi cores machine

@thangngoc89 that's seems like a pretty impressive tool. I want to check that.

@arunoda It's a drop-in replacement (at least for me). Build time dropped from 3 minutes to 1 minute in a 8 cores machine. But the machine is frozen during build time :sweat_smile:

I have found the constructor name hack too, then I tried to use one of the parallel UglifyJs solution, but it didn't work, the build time is still the same with sequential UglifyJs.

import UglifyJsParallelPlugin from 'webpack-uglify-parallel'
import { cpus } from 'os'

module.exports = {
  webpack (config, { dev }) {
    if (!dev) {
      const opts = config.plugins.filter(p => p.constructor.name === 'UglifyJsPlugin')[0].options
      config.plugins = config.plugins.filter(p => p.constructor.name !== 'UglifyJsPlugin')
      config.plugins.push(new UglifyJsParallelPlugin({
        workers: cpus().length,
        ...opts
      }))
    }

    return config
  }
}

~Both build with and without the hack are done in \~20s in my machine. I'm not sure why, shouldn't parallelizing be faster a little bit?~

Whoops, I've got the wrong plugin, here's the right one.

import ParallelUglifyPlugin from 'webpack-parallel-uglify-plugin'

module.exports = {
  webpack (config, { dev }) {
    if (!dev) {
      const opts = config.plugins.filter(p => p.constructor.name === 'UglifyJsPlugin')[0].options
      config.plugins = config.plugins.filter(p => p.constructor.name !== 'UglifyJsPlugin')
      config.plugins.push(new ParallelUglifyPlugin({
        cacheDir: '/tmp',
        uglifyJS: opts
      }))
    }

    return config
  }
}

Now, my build time is reduced drastically by ~50% ephemerally (because the caches will reset after reboot). Seems good to me!

I started to use a substitute of a newer version that allows me to minify ES6.

npm install [email protected] <- check for recent version, for me it was 1.0.0-beta.1

const Uglify = require('uglifyjs-webpack-plugin');

module.exports = {
  webpack: function (c) {
    c.plugins = c.plugins.filter(
      (plugin) => (plugin.constructor.name !== 'UglifyJsPlugin')
    )

    c.plugins.push(
      new Uglify()
    );

    return c;
  }
}

Thanks for all of the hardwork Zeit! Really excited to migrate a large application over to NextJS .. For some reason, I've started to experience this problem. Disabling UglifyJS is definitely not an optimal solution on my end. Has anyone gotten to the bottom of this? I'm getting mixed results from what you suggested @rwieruch

What do you mean by mixed results @hanford ? The workaround should only discard the default Uglify version and use instead a more recent version.

@rwieruch So I can build my app locally using uglifyjs-webpack-plugin and it works fine. However I get a pretty obscure "Build Error" when building remotely via now.

I tried using webpack-parallel-uglify-plugin and the result was even weirder, seemed like my entire app was uglified, except for all scripts the next/_pages/*

My environment is totally up to date as well

node: 9.0.0
next: 4.1.4
webpack: 3.8.1

@jlei523 thanks for sharing, definitely watching/reading those 馃檶

@arunoda According to the stats on https://github.com/babel/minify, uglify is now faster that babel-minify on all benchmarks and can also transform ES6 to ES6. The only one faster than this would be butternut, but they are still experimental.

Superseded by our work in canary

@rauchg Where should we look to find information on that? Sounds exciting!

Was this page helpful?
0 / 5 - 0 ratings