webpack: Webpack 4 doesn't support sharing modules from entries

Do you want to request a feature or report a bug?

Bug

What is the current behavior?

Webpack 4.5 cannot be configured to create JavaScript files that both: a) bundle shared modules and b) runs them.

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

The following markup shows the use case of a large app that has pages that assumes a babel-polyfilled environment and may depend on either React or jQuery or both. A page often has a corresponding Webpack entry that shouldn’t redundantly bundle babel-polyfill, React, jQuery, or the Webpack runtime.

<html>
  <body>
    <!-- Download and run babel-polyfill, fetch polyfill modules, and webpack runtime. -->
    <script src="environment.js"></script>
    <!-- An ES6 environment should polyfilled -->
    <script>console.assert('Map exists.', typeof Map === 'function');</script>
    <script>console.assert('Set exists.', typeof Set === 'function');</script>

    <!-- Download react, react-dom -->
    <script src="vendor-react.js"></script>

    <!-- Download and run jquery, and a module that sets it on window -->
    <script src="vendor-jquery.js"></script>
    <script>console.assert('jQuery exists.', typeof jQuery === 'function');</script>

    <!-- Download and run page-specific code that may import react or jquery
      without bundling react or jquery in page-a.js -->
    <script src="page-a.js"></script>
  </body>
</html>

What is the expected behavior?

Webpack 3 and it’s CommonsChunkPlugin supported this feature.

Please mention other relevant information such as the browser version, Node.js version, webpack version, and Operating System.

Webpack 4.5, Node 8.2, OS X 10.12.6.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 19
  • Comments: 39 (9 by maintainers)

Commits related to this issue

Most upvoted comments

To expand on my last comment, by the way, there’s a slightly undocumented syntax for optimization.runtimeChunk: You can actually set it to

optimization: {
  runtimeChunk: {
    name: 'shared',
  },
}

to have Webpack inject the runtime into that chunk; this interoperates with optimization.cacheGroups.***.name, so if you have a shared chunk of dependencies that you swear every page of yours will load, you can use the above configuration to have Webpack inject the runtime there, to avoid the extra runtime-only chunk.

If I understand your problem correctly, you are trying to load multiple entry points on a single page. This should work if you create one runtimeChunk for all entry points.

optimization: {
  runtimeChunk: 'single'
}

This creates a “runtime.js”, which you need to load as first script on the page. After that, you can load multiple entry points on your page.

This is actually blocking update to webpack 4 for us. Some background: we have a pretty large app (4500+ modules, ~400 entry points). App is a mix of legacy and modern, webpack-built JS code. Every page could be a mix of legacy and modern widgets. For this cases, we create a separate webpack entry point for every modern-inside-legacy widget. Some global initialization is then put into another init.js entry point:

import 'babel-polyfill';
import SomeLib from 'some-internal-library';

SomeLib.configure({ ...options });

This init.js is executed before widget entry points. Widgets expect 'some-internal-library' to be configured. This worked us for every webpack version since 0.x. On webpack 4 it broke, because it seems like every widget entry point now re-executes some-internal-module on the first import and configure call in init.js does not affect the rest.

Is there something we can do to make it work?

@TheLarkInn When you say “available for other bundles not created from the same compilation?”

Does that apply to this line in the original description?

<script>console.assert('Map exists.', typeof Map === 'function');</script>

If so, do you think that this use case is unreasonable or unrealistic?

It seems to me that Webpack 4 assumes that it is responsible for the loading of all JavaScript on a site. While this may be true with code splits in a SPA, this is not always true for a site composed of html pages with script tags.

The hueristics system to determine what commons files to generate is a nice feature for SPA, but it is not practical for an html page app with script tags, where each script tag must be deliberately added.

I’m trying to avoid:

  • Bundling jQuery or React for pages that do not need it.
  • Downloading common modules more than once.
  • Downloading the Webpack runtime more than once.
  • Having common modules pulled into new, unexpected files (often named something like a~b~c.js).

I’m trying to preserve:

  • My existing script tags throughout a large app.
  • The ability to include babel-polyfill for any JavaScript that runs after it.
  • The optimization of downloading React or jQuery once, in their own common script files, that are made available to other Webpack modules or to the global window.

Is this something that Webpack 4 supports?

This is really a nightmare for me, I nearly spent around 30 hours to make it work somehow - and it’s still not working. I’m using rails webpacker and either the chunks are loaded multiple times or the JS code is not executing at all - even though I can see the “working” javascript include tags in the html. Until now webpack is absolutely killing my nerves & productivty 😦

Basically I would like to have 1 base js file (imports polyfill, jquery,…), and several other js entry files which are included if one of our subpages is opened (different react components, depending on which site is opened)).

This issue is more than a year old, and gets extremely frustrating for many devs trying to use a singleton-like module usage.

Webpack devs, is there not a solution in sight? As it is now, webpack 4 does not have an upgrade path for us.

You can set:

        optimization: {
            splitChunks: {
                chunks: 'all',
            },
        },

Which tells webpack to optimize dependencies from entries as well. By default chunks is set to async, so webpack won’t touch your entries.

see https://webpack.js.org/plugins/split-chunks-plugin/#splitchunkschunks and https://webpack.js.org/guides/code-splitting

Any updates on this problem? This was something working smoothly in earlier version of webpack.

I have a vendor bundle, and my own commons module. With libraryTarget: 'var' I used to configure variables in my commons module (it has an options variable within, which is exported for the modules importing common.js).

Before 4.0, using CommonsChunkPlugin I could import the common.js module, then configure it from inlined code in my pages, and then use the options configured from the inline code in my modules. After 4.0, I can’t get it working. It seems as the options within the same common.js is two separate variables: one which is configured via the libraryTarget behavior from the inlined code, and another which is basically an the original state of the defined options variable in common.js, imported by the underlying modules.

Reading the new configuration docs so far with SplitChunks, I could not find ways as to how to keep the ‘one parent’-‘many children’ relation, so that my modules would see the same inline configured common.js option variable everywhere.

Am I missing something, or has this become completely unavailable with V4? Can someone enlighten me as to how I can achieve what was available with V3?

@sokra care to expand a bit on the different way?

It sounds like you are trying to synthetically make certain modules and things available for other bundles not created from the same compilation?

To expand on my last comment, by the way, there’s a slightly undocumented syntax for optimization.runtimeChunk: You can actually set it to

optimization: {
  runtimeChunk: {
    name: 'shared',
  },
}

to have Webpack inject the runtime into that chunk; this interoperates with optimization.cacheGroups.***.name, so if you have a shared chunk of dependencies that you swear every page of yours will load, you can use the above configuration to have Webpack inject the runtime there, to avoid the extra runtime-only chunk.

You are the true god of webpack!

@sokra can you please help us out here?

I’m also struggling with this. I have 3 entrypoints - preload, polyfill, and application.

preload is responsible for checking the browser supports promises, loading the polyfill entrypoint if not, and then loading the application entrypoint. It loads these via regular old document.createElement(“script”) so that it’s not reliant on webpack’s promise-based chunk loader. I disable code splitting in preload & polyfill for the same reason.

config.entry = {
  preload: "./src/preload",
  polyfill: "./src/polyfill",
  application: "./src/application"
};
config.optimization.splitChunks = {
  chunks: chunk => chunk.name !== "preload" && chunk.name !== "polyfill",
};
config.optimization.runtimeChunk = {
  name: "preload"
};

This bit works fine, and webpack’s runtime chunk is successfully only inserted into preload.js. However any dependencies loaded in both preload & application get duplicated - once in the preload entrypoint, and once in application’s split modules. How can I persuade webpack that any modules referenced in the runtimeChunk entrypoint don’t need to be duplicated?

@sergei-startsev thanks I’ll look into those next if this doesn’t work, it seems that it might work though:

optimization: {
  splitChunks: {
    chunks: 'all',
    name: 'base',
  }
}

This is creating a base.js of node_modules, and later if I want to add other local shared resources I can modify chunks or maybe cacheGroups (not sure which)

@deleteme Vendor files are not entry points… forget about the CommonsChunkPlugin in webpack 4.

This is also supported in webpack 4 but in a different way