Next.js: Eliminate `core-js` from “Hello World” Next.js applications

Created on 7 Apr 2019  ·  29Comments  ·  Source: vercel/next.js

As discussed with @Timer, core-js is not needed in most cases. He can add in more context here.

Most helpful comment

I did a bit of investigation why corejs ends up inside the bundles. It has nothing to do with the webpack or babel configuration that is used for the app, so there is nothing we (the users of next) can actually do to change the current situation.

The actual reason though is pretty straight forward: It's pulled in through the babel runtime polyfills which are used in the next/dist/client/* modules. For example, consider "next/router", which through its dependencies (next/router.js -> next/dist/client/router.js) eventually imports "@babel/runtime-corejs2/helpers/interopRequireWildcard", "@babel/runtime-corejs2/helpers/interopRequireDefault", "@babel/runtime-corejs2/helpers/extends", and "babel/runtime-corejs2/core-js/object/define-property". These are CJS modules, so therefore not treeshakeable, and all end up in the bundle. Together these are ~47kB, which pretty closely matches the ~49kB that I'm seeing in the output from the bundle analyzer.

I think the only solution is to not publish next/client/* as commonjs but instead publish those modules as ESM, and then process them through webpack so that babel can inject the appropriate polyfills. And also not inject the babel runtime polyfills in those published modules. It should be up the the end consumer of next to decide which polyfills are need, this is not something that the next team can decide at the time they publish the next package to npm.

All 29 comments

@timneutkens I'm assuming caching is the reason core-js is used in full here, rather than going with @babel/preset-env's "useBuiltIns":"entry" option?

/cc @keanulee

Is it currently possible not to include core-js in the compiled JS files at all?
I think I don't need it, my app only targets the evergreen browsers.

As an alternative, you could consider bumping up to using core-js 3 (via babel) which allows tree-shaking and targeted inclusions: https://github.com/zloirock/core-js#babelpreset-env

There's also a great external polyfill service (https://polyfill.io/v3/) which we use extensively in my org (self-hosted in our case) to avoid unnecessary bloat over-the-wire. Would be interesting to consider a Next that lists what might need to be polyfilled (either via that service or hard-coded polyfills brought by the user) but does not necessarily include its own solution.

I would love to remove core-js completely from my bundles.

I'm importing barely any JS at all other than React and Next.js is still loading the entirety of core-js which seems incredibly wasteful to me, especially for my uses case where I don't care about support for older browsers.

any solutions ?

@merrywhether I am curious to know how are you guys using polyfill.io with the next.js project. That's what I am trying to do in a project but didn't found good examples yet.

@vjs3 I'm using it by extending Document and placing a script tag before the next script:

import Document_, { Head, Main, NextScript } from 'next/document'

const polyfillFeatures = [
  'default',
  'Array.prototype.find',
  'Array.prototype.includes',
]

export default class Document extends Document_ {
  render () {
    return (
      <html>
        <Head />
        <body>
          <Main />

          {/* Just the polyfills you need for your site, tailored to each browser. */}
          <script type='text/javascript' src={`https://cdn.polyfill.io/v2/polyfill.min.js?features=${polyfillFeatures.join(',')}`} />

          {/* Site Scripts */}
          <NextScript />
        </body>
      </html>
    )
  }
}

@vjs3 We do almost the same as @LinusU, except we add an additional entry in <Head /> to generate a <link rel="preload" as="script href={polyfillHref}" /> to help the browser get the script sooner and speed up app boot.

Thanks a lot @LinusU and @merrywhether for your detailed explanation. I am using the same implementation as you suggested @merrywhether.

Any solutions about removing core-js from bundles? It's really not needed in most cases!

module.exports = { experimental: { modern: true } } would help a bit.

@timneutkens in a lastest version?

Canary, note that it's experimental.

@timneutkens I can't confirm that. Even if I set modern:true, core-js remains in the bundles (both the module and nomodule bundles).

Another source of overhead is the polyfills.js file which currently (as of version 9.2.0) contains fetch and url polyfills, they are not needed in modern browsers either.

Another source of overhead is the polyfills.js file which currently (as of version 9.2.0) contains fetch and url polyfills, they are not needed in modern browsers either.

This is not loaded in modern browsers by means of the nomodule property.

I stand corrected. I was only looking at the output from the bundle analyzer which shows two polyfills bundles. Both are generated but the polyfills-XXX.module.js is not actually being loaded into a modern browser.


bundle analyzer screenshot of an empty next.js v9.2.1-canary.2 app with modern:true
Screen Shot 2020-01-18 at 13 22 17

I did a bit of investigation why corejs ends up inside the bundles. It has nothing to do with the webpack or babel configuration that is used for the app, so there is nothing we (the users of next) can actually do to change the current situation.

The actual reason though is pretty straight forward: It's pulled in through the babel runtime polyfills which are used in the next/dist/client/* modules. For example, consider "next/router", which through its dependencies (next/router.js -> next/dist/client/router.js) eventually imports "@babel/runtime-corejs2/helpers/interopRequireWildcard", "@babel/runtime-corejs2/helpers/interopRequireDefault", "@babel/runtime-corejs2/helpers/extends", and "babel/runtime-corejs2/core-js/object/define-property". These are CJS modules, so therefore not treeshakeable, and all end up in the bundle. Together these are ~47kB, which pretty closely matches the ~49kB that I'm seeing in the output from the bundle analyzer.

I think the only solution is to not publish next/client/* as commonjs but instead publish those modules as ESM, and then process them through webpack so that babel can inject the appropriate polyfills. And also not inject the babel runtime polyfills in those published modules. It should be up the the end consumer of next to decide which polyfills are need, this is not something that the next team can decide at the time they publish the next package to npm.

Any update on this one? Core-js takes an awful lot of space in my bundles, included in the same chunk as some src code:

Screen Shot 2020-05-23 at 3 15 55 PM

Thanks!

Actually, using this babel config seems to resolve it:

.babelrc

{
  "presets": [
    [
      "next/babel",
      {
        "preset-env": {
          "useBuiltIns": "entry",
          "corejs": "3.6"
        }
      }
    ]
  ]
}

.browserslistrc

last 2 versions
> 0.25%
not dead
not IE 11

@Danetag Do you import "core-js"; anywhere in your project? Because if not, you are not loading the core-js polyfills at all, not even those required for the browsers which match your browserslistrc.

See the documentation for useBuiltIns.

This has actually been fixed! Any core-js present in your application is due to your application code or a dependency.

https://github.com/zeit/next.js/issues/6931#issuecomment-633158748

FWIW don't make these changes unless you know the exact semantics of how it works, by adding this it adds every core-js polyfill needed into the bundle sent to all browsers which is not needed as Next.js automatically polyfills older browsers covered by that setting and only loads that polyfill JS in browsers that don't support <script type="module"> (legacy browsers)

@timneutkens I'm no Babel expert, and you're right, according to the doc it's supposed to do exactly what you described.

But for some reasons, it seems that core-js is removed with "useBuiltIns": "entry", but not "useBuiltIns": "usage".

With "useBuiltIns": "usage":

Screen Shot 2020-05-24 at 11 16 58 AM
Screen Shot 2020-05-24 at 11 16 33 AM
Screen Shot 2020-05-24 at 11 16 38 AM

With "useBuiltIns": "entry":

Screen Shot 2020-05-24 at 11 17 04 AM
Screen Shot 2020-05-24 at 11 15 33 AM
Screen Shot 2020-05-24 at 11 15 39 AM

I'm confused 🤷 If you have any tips, that would be great!

Use this babelrc:

{
  "presets": [
    [
      "next/babel"
    ]
  ]
}

All the config you had is not needed and can only cause larger bundles where not needed.

@timneutkens Awesome! Thanks you 🔥

@timneutkens Now I remember the context of why we added this custom config. We just dropped the support for IE 11, but it wasn't the case at the beginning of the project. We encountered this error in IE console:

"Object doesn't support property or method 'repeat'"

And the only way to make it work was this update. Anyway, it's a separate issue that we don't have anymore, but maybe something to be aware of!

Thanks again 👍

Now I remember the context of why we added this custom config. We just dropped the support for IE 11, but it wasn't the case at the beginning of the project. We encountered this error in IE console:

When was that? We changed the way Next.js polyfills legacy browsers a while back and the specific case you're seeing should be fixed already 👍

@timneutkens About 4 weeks ago, using next v9.3.4, Node v12.16.1

Was this page helpful?
0 / 5 - 0 ratings