简体   繁体   中英

Webpack 4 migration CommonsChunkPlugin

I need help migrating the following code from webpack 3 to 4.

new webpack.optimize.CommonsChunkPlugin({
    minChunks: module => module.context && module.context.indexOf("node_modules") !== -1,
    name: "vendor",
    chunks: ["main"]
})

I have two entry files and want only the dependencies of the first one to be included in the vendor chunk. The dependencies of the second entry should all stay in its own bundle.

As of webpack v4 the CommonsChunkPlugin is deprecated.

We have deprecated and removed CommonsChunkPlugin, and have replaced it with a set of defaults and easily overridable API called optimization.splitChunks .

webpack.optimize.CommonsChunkPlugin has been removed, 
please use config.optimization.splitChunks instead.

Deprecated

You no longer need to use these plugins:

DedupePlugin has been removed too in v4

NoEmitOnErrorsPlugin -> optimization.noEmitOnErrors (on by default in production mode) ModuleConcatenationPlugin -> optimization.concatenateModules (on by default in prod mode) NamedModulesPlugin -> optimization.namedModules (on by default in dev mode)


Recommendations for webpack 4

Use mini-css-extract-plugin instead of text-extract-plugin . Use webpack-bundle-analyzer to analyze your bundled output in graphical way.

Entry scripts are real "Entry-Scripts" to your application, don't add vendor files explicitly to entry: in webpack.config.js . SPA apps have one entry and Multi-Page-Apps like classic ASP.NET MVC apps have multiple entry points. Webpack will build a dependenc graph out of your entry scripts and generate optimized bundles for your app.

If you want to migrate from an older webpack version, it's best to checkout the migration guide

Tree shaking (dead code elimination) is only enabled in production mode.


Webpack 4, the new way of bundling assets

( You have to remove your CommonsChunkPlugin-thinking from your head )

,!! Meanwhile the webpack doc has been updated, a section SplitChunks was added !!!

It follows a new philosophy :

Webpack 4 now by default does optimizations automatically. It analyzes your dependency graph and creates optimal bundles (output), based on the following conditions:

  1. New chunk can be shared OR modules are from the node_modules folder
  2. New chunk would be bigger than 30kb (before min+gz)
  3. Maximum number of parallel request when loading chunks on demand <= 5
  4. Maximum number of parallel request at initial page load <= 3

All this can be tweaked using the SplitChunksPlugin! ( see SplitChunksPlugin documentation )

A more detailed explanation on how to use the new optimization.splitChunks API.



CommonsChunkPlugin was removed because it has a lot of problems:

  • It can result in more code being downloaded than needed.
  • It's inefficient on async chunks.
  • It's difficult to use.
  • The implementation is difficult to understand.

The SplitChunksPlugin also has some great properties:

  • It never downloads unneeded module (as long you don't enforce chunk merging via name)
  • It works efficient on async chunks too
  • It's on by default for async chunks
  • It handles vendor splitting with multiple vendor chunks
  • It's easier to use
  • It doesn't rely on chunk graph hacks
  • Mostly automatic

--> Source


Regarding your issue , you want to split all deps of entry1 and entry2 into separate bundles.

      optimization: {
        splitChunks: {
          cacheGroups: {   
            "entry1-bundle": {
              test: /.../,   // <-- use the test property to specify which deps go here
              chunks: "all",
              name: "entry1-bundle",
 /** Ignore minimum size, minimum chunks and maximum requests and always create chunks for this cache group */
              enforce: true,
              priority: ..  // use the priority, to tell where a shared dep should go
            },
            "entry2-bundle": {
              test: /..../, // <-- use the test property to specify which deps go here
              chunks: "all",
              name: "entry2-bundle",
              enforce: true,
              priority: ..
            }
          }
        }
      },

If you don't add the optimization:splitChunks entry the default setting is as follows :

splitChunks: {
  chunks: 'async',
  minSize: 30000,
  minRemainingSize: 0,
  maxSize: 0,
  minChunks: 1,
  maxAsyncRequests: 6,
  maxInitialRequests: 4,
  automaticNameDelimiter: '~',
  automaticNameMaxLength: 30,
  cacheGroups: {
    vendors: {
      test: /[\\/]node_modules[\\/]/,
      priority: -10
    },
    default: {
      minChunks: 2,
      priority: -20,
      reuseExistingChunk: true
    }
  }
}

You can set optimization.splitChunks.cacheGroups. default to false to disable the default cache group, same for vendors cache group!

Here are some other SplitChunks configuration examples with explanation.


Up-to-date interface implementations for SplitChunksOptions , CachGroupOptions and Optimization can be found here .

The interface definitions below may not be 100% accurate, but good for a simple overview:

SplitChunksOptions interface

interface SplitChunksOptions { /** Select chunks for determining shared modules (defaults to \"async\", \"initial\" and \"all\" requires adding these chunks to the HTML) */ chunks?: "initial" | "async" | "all" | ((chunk: compilation.Chunk) => boolean); /** Minimal size for the created chunk */ minSize?: number; /** Minimum number of times a module has to be duplicated until it's considered for splitting */ minChunks?: number; /** Maximum number of requests which are accepted for on-demand loading */ maxAsyncRequests?: number; /** Maximum number of initial chunks which are accepted for an entry point */ maxInitialRequests?: number; /** Give chunks created a name (chunks with equal name are merged) */ name?: boolean | string | ((...args: any[]) => any); /** Assign modules to a cache group (modules from different cache groups are tried to keep in separate chunks) */ cacheGroups?: false | string | ((...args: any[]) => any) | RegExp | { [key: string]: CacheGroupsOptions }; }

CacheGroupsOptions interface:

interface SplitChunksOptions {
    /** Select chunks for determining shared modules (defaults to \"async\", \"initial\" and \"all\" requires adding these chunks to the HTML) */
    chunks?: "initial" | "async" | "all" | ((chunk: compilation.Chunk) => boolean);
    /** Minimal size for the created chunk */
    minSize?: number;
    /** Minimum number of times a module has to be duplicated until it's considered for splitting */
    minChunks?: number;
    /** Maximum number of requests which are accepted for on-demand loading */
    maxAsyncRequests?: number;
    /** Maximum number of initial chunks which are accepted for an entry point */
    maxInitialRequests?: number;
    /** Give chunks created a name (chunks with equal name are merged) */
    name?: boolean | string | ((...args: any[]) => any);
    /** Assign modules to a cache group (modules from different cache groups are tried to keep in separate chunks) */
    cacheGroups?: false | string | ((...args: any[]) => any) | RegExp | { [key: string]: CacheGroupsOptions };
}

Optimization Interface

interface CacheGroupsOptions {
    /** Assign modules to a cache group */
    test?: ((...args: any[]) => boolean) | string | RegExp;
    /** Select chunks for determining cache group content (defaults to \"initial\", \"initial\" and \"all\" requires adding these chunks to the HTML) */
    chunks?: "initial" | "async" | "all" | ((chunk: compilation.Chunk) => boolean);
    /** Ignore minimum size, minimum chunks and maximum requests and always create chunks for this cache group */
    enforce?: boolean;
    /** Priority of this cache group */
    priority?: number;
    /** Minimal size for the created chunk */
    minSize?: number;
    /** Minimum number of times a module has to be duplicated until it's considered for splitting */
    minChunks?: number;
    /** Maximum number of requests which are accepted for on-demand loading */
    maxAsyncRequests?: number;
    /** Maximum number of initial chunks which are accepted for an entry point */
    maxInitialRequests?: number;
    /** Try to reuse existing chunk (with name) when it has matching modules */
    reuseExistingChunk?: boolean;
    /** Give chunks created a name (chunks with equal name are merged) */
    name?: boolean | string | ((...args: any[]) => any);
}

I have two entry files and want only the dependencies of the first one to be included in the vendor chunk. The dependencies of the second entry should all stay in its own bundle.

Assuming your entrypoints are main and secondary :

entry: {
    main: 'path-to/main.js',
    secondary: 'path-to/secondary.js'
}

Using You can extract only the vendors modules from main chunk but leave other third parties modules referenced in secondary inside that chunk using the test function of the cacheGroups you want to create.

optimization: {
    splitChunks: {
        cacheGroups: {
            vendors: {
                name: 'vendors',
                chunks: 'all',
                reuseExistingChunk: true,
                priority: 1,
                enforce: true,
                test(module, chunks) {
                    const name = module.nameForCondition && module.nameForCondition();
                    return chunks.some(chunk => {
                        return chunk.name === 'main' && /[\\/]node_modules[\\/]/.test(name);
                    });
                }
            },
            secondary: {
                name: 'secondary',
                chunks: 'all',
                priority: 2,
                enforce: true,
                test(module, chunks) {
                    return chunks.some(chunk => chunk.name === 'secondary');
                }
            }
        }
    }
}

This took me a while to figure out, but the key realization for me was that the chunks argument in webpack 4 now takes a function, which allows you to only include a specific entry. I'm assuming this is a recent change, because at the time of posting it wasn't in the official documentation.

splitChunks: {
  cacheGroups: {
    vendor: {
      name: 'vendor',
      chunks: chunk => chunk.name == 'main',
      reuseExistingChunk: true,
      priority: 1,
      test: module =>
        /[\\/]node_modules[\\/]/.test(module.context),
      minChunks: 1,
      minSize: 0,
    },
  },
},

Please note that I corrected the issue by changing this in my webpack.common.js:

  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: ['vendor']
    })
    ]

To this:

  optimization: {
      runtimeChunk: "single", // enable "runtime" chunk
      splitChunks: {
          cacheGroups: {
              vendor: {
                  test: /[\\/]node_modules[\\/]/,
                  name: "vendor",
                  chunks: "all"
              }
          }
      }
  },

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM