Webpack 2 Dynamic Import degrades build performance (slow) compared to static imports.

Do you want to request a feature or report a bug?
I would like to report a bug.

What is the current behavior?
When converting my project’s components to dynamically import components instead of using static import statements, the build time increases by 10x and incremental builds by about the same.

The dynamic import functionality has reduced my bundle size astronomically by breaking all of my react components into separate chunks that get loaded into the app on-demand. I used a react-loader setup to replace most of my component static imports with dynamic imports.

If the current behavior is a bug, please provide the steps to reproduce.

  1. Create a large number static imports in your project.
  2. Convert those imports into dynamic imports.
  3. Run webpack-dev-server with hot-reloading for both and compare the build time.
//Static Example: index.js
import ComponentA from './component-a';
import ComponentB from './component-b';

export { ComponentA, ComponentB }

//Dynamic Example: index.js
import Loadable from 'react-loadable';

const LoadingComponent = f => {
  return (
    <div style={{textAlign:'center'}}><i className="fa fa-spinner fa-spin fa-2x"></i></div>
  )
}

const LoadableComponent = opts => {
  return Loadable({
    delay: 300,
    LoadingComponent,
    ...opts,
  })
}

const ImportLoadable = (loader) => {
  const Component = LoadableComponent({
    loader
  });

  return Component;
}

const ComponentA = ImportLoadable(f => import('./component-a'));
const ComponentB = ImportLoadable(f => import('./component-b'));

export { ComponentA, ComponentB }

What is the expected behavior?
I expect the dynamic import build time to be the same as a static import build time.

If this is a feature request, what is motivation or use case for changing the behavior?
It seems that even though the dynamic import automatically chunks your bundle into multiple files, that when changing the underlying code for one of those chunks, webpack should be able to detect which file that was and only rebuild that file and hot-reload it.

Please mention other relevant information such as the browser version, Node.js version, webpack version and Operating System.
Webpack 2.3.2
Webpack-Dev-Server 2.4.2

Author: Fantashit

4 thoughts on “Webpack 2 Dynamic Import degrades build performance (slow) compared to static imports.

  1. I’ve created a small repo to help reproduce the problem here.

    Just clone and npm install, then you can run two commands:

    • npm run import: run the web server using dynamic imports;
    • npm run require: run the web server using require.ensure syntax

    The repo and the files included are very small so the difference is not that big but if you modify a random file it’s usually taking ~300/400ms to rebuild with require.ensure versus ~700/800ms with import (the problem is way more evident in real apps).

    Also if you edit a file with require.ensure here’s the output:

    webpack: Compiling...
    Hash: 01801f65cbafafb802d7
    Version: webpack 2.5.1
    Time: 442ms
                                       Asset       Size  Chunks                    Chunk Names
                               0.requires.js    2.92 MB       0  [emitted]  [big]
                                 requires.js     346 kB       1  [emitted]  [big]  main
        0.3e7d4048a4682840be2d.hot-update.js  844 bytes       0  [emitted]
        3e7d4048a4682840be2d.hot-update.json   43 bytes          [emitted]
                           0.requires.js.map    3.08 MB       0  [emitted]
    0.3e7d4048a4682840be2d.hot-update.js.map  478 bytes       0  [emitted]
                             requires.js.map     408 kB       1  [emitted]         main
    webpack: Compiled successfully.
    

    versus this using import:

    webpack: Compiling...
    Hash: 8bbc15d48815f50afd15
    Version: webpack 2.5.1
    Time: 747ms
                                        Asset       Size  Chunks                    Chunk Names
         d71b0f87e6ade357067e.hot-update.json   44 bytes          [emitted]
                                 0.imports.js    1.96 MB       0             [big]
                                 2.imports.js    1.49 MB       2             [big]
                                 3.imports.js    1.49 MB       3             [big]
                                 4.imports.js    1.49 MB       4             [big]
                                 5.imports.js    1.49 MB       5             [big]
                                 6.imports.js    1.49 MB       6             [big]
                                 7.imports.js    1.49 MB       7             [big]
                                 8.imports.js    1.49 MB       8             [big]
                                 9.imports.js    1.49 MB       9             [big]
                                10.imports.js    1.49 MB      10             [big]
                                11.imports.js    1.49 MB      11             [big]
                                12.imports.js    1.49 MB      12             [big]
                                13.imports.js    1.49 MB      13             [big]
                                14.imports.js    1.49 MB      14             [big]
                                15.imports.js    1.49 MB      15             [big]
                                16.imports.js    1.13 MB      16             [big]
                                17.imports.js     681 kB      17             [big]
                                18.imports.js     141 kB      18
                                19.imports.js     141 kB      19
                                20.imports.js     141 kB      20
                                21.imports.js     141 kB      21
                                22.imports.js     141 kB      22
                                23.imports.js     141 kB      23  [emitted]
                                24.imports.js     141 kB      24
                                   imports.js     421 kB      25  [emitted]  [big]  main
        23.d71b0f87e6ade357067e.hot-update.js  850 bytes      23  [emitted]
                                 1.imports.js    1.49 MB       1             [big]
                             0.imports.js.map    2.05 MB       0
                             1.imports.js.map    1.47 MB       1
                             2.imports.js.map    1.47 MB       2
                             3.imports.js.map    1.47 MB       3
                             4.imports.js.map    1.47 MB       4
                             5.imports.js.map    1.47 MB       5
                             6.imports.js.map    1.47 MB       6
                             7.imports.js.map    1.47 MB       7
                             8.imports.js.map    1.46 MB       8
                             9.imports.js.map    1.47 MB       9
                            10.imports.js.map    1.47 MB      10
                            11.imports.js.map    1.47 MB      11
                            12.imports.js.map    1.47 MB      12
                            13.imports.js.map    1.47 MB      13
                            14.imports.js.map    1.47 MB      14
                            15.imports.js.map    1.47 MB      15
                            16.imports.js.map    1.28 MB      16
                            17.imports.js.map     812 kB      17
                            18.imports.js.map     169 kB      18
                            19.imports.js.map     169 kB      19
                            20.imports.js.map     169 kB      20
                            21.imports.js.map     169 kB      21
                            22.imports.js.map     169 kB      22
                            24.imports.js.map     169 kB      24
                            23.imports.js.map     168 kB      23  [emitted]
    23.d71b0f87e6ade357067e.hot-update.js.map  481 bytes      23  [emitted]
                               imports.js.map     502 kB      25  [emitted]         main
    webpack: Compiled successfully.
    
  2. From a brief chat with @sokra, my impression that #4636 (comment) does not actually demonstrate a perf regression, but rather the difference in how require.ensure() and import() work. (Correct me if I’m wrong.)

    With require.ensure(), anything you require() in the block becomes a part of one chunk. So this example puts all the “dynamic” modules (which we have a lot if we use dynamic expressions) in one JS chunk. This seems worse for the app performance, as, for example, if you load locale files based on current language with a statement like this, you’re going to load all of them in one file. But it’s faster to process at build time. This behavior seems optimal if bundles contain a lot of common code, and so it’s not a big deal to combine them together.

    With import(), however, every single possible match gets its own chunk. This seems better for the app performance, and it maps more intuitively to how you’d expect it to work, but is worse for the build performance because of all those chunks. This behavior seems optimal if bundles are different enough and contain little common code (of if you rarely need to switch them).

    So require.ensure() default behavior seems to work better for things like component trees, but import() default behavior seems to work better for things like locales.

    Frankly, I don’t know what the intended solution is here, but I’d love to hear more from Webpack folks on how this feature should be used, and what the intended performance is.

    Apparently the import() default behavior is different from require.ensure() default behavior, but you will be able to customize it with hints to the import() call: #4807 (comment).

  3. @gaearon, this makes quite a bit of sense. My though for a solution is to have an option that allows us to disable dynamic import and require.ensure in development. The main reason I use import and require.ensure are to chunk in production.

    We are willing to have a longer build time for pushing to production, but it is debilitating for development.

  4. is this applicable for latest versions as well ?
    I am using webpack 3.5.x and recently modified implementation for Dynamic Import using react-loadable and feel that build time has been increased.

    Please suggest better solution to do so

Comments are closed.