Razzle: Vendor bundling example

Created on 21 Sep 2018  路  16Comments  路  Source: jaredpalmer/razzle

The vendor bundling example doesn't appear to work as intended. I pulled and ran it and although everything server-rendered fine the output bundles look far too small鈥擱eact + React DOM can't be 6kb total. Also the client-side code just doesn't run鈥攖he click-handler in the example doesn't appear to do anything.

razzle-bug

I reproduced this in my own Razzle project too by copying the same config over. I'm not sure exactly what's going on because I'm far from a webpack expert but I know webpack 4 made a lot of changes to commons chunks etc, so is it possible that broke this?

Also probably unrelated to this but the example code in the README doesn't match the project code (and appear to not even build correctly)

Hacktoberfest stale

Most helpful comment

Well, got it done. No idea, why I didn't before. Here's my working config.
If anyone has any suggestions to optimize this, I'd love to hear it.

if (target === 'web') {

    newConfig.output.filename = dev
        ? 'static/js/[name].js'
        : 'static/js/[name].[hash:8].js';

    newConfig.optimization = {
        ...newConfig.optimization,
        splitChunks: {
            cacheGroups: {
                // Chunk splitting optimiztion
                dte: {
                    test: /[\\/]node_modules[\\/](luxon|@date-io\/luxon)[\\/]/,
                    name: 'dte',
                    reuseExistingChunk: true,
                    priority: -30,
                },
                redux: {
                    test: /[\\/]node_modules[\\/](redux|react-redux)[\\/]/,
                    name: 'redux',
                    reuseExistingChunk: true,
                },
                graphql: {
                    test: /[\\/]node_modules[\\/](graphql|react-apollo|apollo-cache-inmemory|apollo-client|apollo-link|apollo-link-context|apollo-link-http|apollo-link-ws|apollo-utilities)[\\/]/,
                    name: 'apollo',
                    reuseExistingChunk: true,
                },
                form: {
                    test: /[\\/]node_modules[\\/](yup|react-final-form|final-form)[\\/]/,
                    name: 'form',
                    reuseExistingChunk: true,
                    priority: -30,
                },
                vendor: {
                    test: /[\\/]node_modules[\\/](react|react-dom|@lingui|react-router|react-router-dom)[\\/]/,
                    name: 'vendor',
                    reuseExistingChunk: true,
                },
                default: {
                    minChunks: 2,
                    priority: -20,
                    reuseExistingChunk: true,
                },
            },
        },
    };
}

All 16 comments

Same here, doesn't work.

Hola! So here's the deal, between open source and my day job and life and what not, I have a lot to manage, so I use a GitHub bot to automate a few things here and there. This particular GitHub bot is going to mark this as stale because it has not had recent activity for a while. It will be closed if no further activity occurs in a few days. Do not take this personally--seriously--this is a completely automated action. If this is a mistake, just make a comment, DM me, send a carrier pidgeon, or a smoke signal.

Vendor bundling is pretty important, so would be good to get this fixed. I might have some time to look at it in the next few weeks

Hi, this example still doesn't work. I can't figure it out why. Vendor bundle doesn't include any react code, same with other libs. Please, can somebody help me with this? Thanks

Review the documentation on splitChunks and vendor bundling. That should help you out.

Scratch that. Tried a number of things and couldn't get splitChunks to work. Don't have time to continue on this right now.

Any updates on this? I really need to get vendor splitting to work as after code splitting with react-loadable, the main bundle size is still 1mb after gzip 馃槶

Okay I am not sure if this solves it but I got vendor splitting to work using cacheGroups instead.
For the with-vendor-bundle example, I did the following:

config.optimization = {
   ...config.optimization,
   splitChunks: {
      cacheGroups: {
         chunks: 'all',
         name: 'vendor',
         test: 'vendor',
      }
   }
}

This thread from stackoverflow helped me.

Followed Webpack site: split chunks example

  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'vendor',
          chunks: 'all',
        }
      }
    }
  }

This worked for me. I can see vendor.chunks.js correctly served.

And we do not even need this part in the example (still work when comment it out), not sure how we deal with razzle/polyfills though

      config.entry.vendor = [
        require.resolve('razzle/polyfills'),
        require.resolve('react'),
        require.resolve('react-dom'),
        // ... add any other vendor packages with require.resolve('xxx')
      ];

If @jaredpalmer can help confirm this is the right way, I can submit PR to update the example

@6thfdwp I can confirm your solution worked for me as well. Thanks for sharing it.

To include razzle/polyfills in your example, I think you should be able to just do this:

vendor: {
  test: /[\\/]node_modules[\\/](razzle|react|react-dom)[\\/]/,
  name: 'vendor',
  chunks: 'all',
}

The way in the docs seems to explicitly state each module to include in the vendor, whereas using test will match based on the directory structure in node_modules. Both ways will work, just different use cases.

Edit: It seems that disables splitting the vendor bundle into chunks, which isn't ideal for production builds (see here). So it will work, but will increase the vendor bundle filesize and prevent it from being chunked up which may not be ideal.

In order to support vendor bundle splitting in production, it seems you need to use webpack's ManifestPlugin, since razzle's assets-webpack-plugin doesn't seem to report them. I came up with this solution:

// Some additional imports:

const ManifestPlugin = require('webpack-manifest-plugin');
const { extname } = require('path');

// Replace the existing AsetsWebpackPlugin with ManifestPlugin:

      config.plugins = config.plugins.map((plugin) => {
        if (plugin.constructor.name === 'AssetsWebpackPlugin') {
          return new ManifestPlugin({
            fileName: '../assets.json',
            writeToFileEmit: true,
            generate: (seed, files) => (
              files.filter(file => !file.isAsset).reduce((manifest, { name, path }) => {
                // Group files by extension in the manifest
                const ext = extname(path).substr(1);
                manifest[ext] = manifest[ext] || [];
                manifest[ext].push(path);
                return manifest;
              }, seed)
            ),
          });
        }

        return plugin;
      });

// Do vendor splitting:

      config.entry.vendor = [
        require.resolve('razzle/polyfills'),
        require.resolve('react'),
        require.resolve('react-dom'),
      ];

      config.optimization = {
        splitChunks: {
          chunks: 'all',
          name: dev ? 'vendor' : false,
        },
      };

You need to specify a name for the splitChunks config in dev, otherwise it will endlessly recompile for some reason.

Then you can import all the assets in your app like so:

  ${assets.css ? assets.css.reduce((styles, asset) => `${styles}<link rel="stylesheet" ref="${asset}">`, '') : ''}

  ${assets.js.reduce((scripts, asset) => `${scripts}<script src="${asset}" defer></script>`, '')}

Perhaps Razzle should migrate to using the ManifestPlugin? I saw it mentioned here, so this may be a good reason to do it.

@scraton Thanks for sharing and explanation.

So what you suggested here will produce more chunks, while the Webpack site example using

   test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
   name: 'vendor',

will always give one chunk vendor.chunks.js as I saw it, include all packages passing the test, react, react-dom etc.

Is this also the reason why the with-vendor-example not working? I am not sure if I understand this correctly, any explanation will be appreciated, Thanks!

I gave it another try to no avail. Using splitChunks leads to endless reloads and no matter what I do, no vendor.js is generated. Tried various .env vars RAZZLE_DEV_BUNDLE_PATH, RAZZLE_BUNDLE_PATH but the manifest always contains a vendor key that contains the standard bundle url

Well, got it done. No idea, why I didn't before. Here's my working config.
If anyone has any suggestions to optimize this, I'd love to hear it.

if (target === 'web') {

    newConfig.output.filename = dev
        ? 'static/js/[name].js'
        : 'static/js/[name].[hash:8].js';

    newConfig.optimization = {
        ...newConfig.optimization,
        splitChunks: {
            cacheGroups: {
                // Chunk splitting optimiztion
                dte: {
                    test: /[\\/]node_modules[\\/](luxon|@date-io\/luxon)[\\/]/,
                    name: 'dte',
                    reuseExistingChunk: true,
                    priority: -30,
                },
                redux: {
                    test: /[\\/]node_modules[\\/](redux|react-redux)[\\/]/,
                    name: 'redux',
                    reuseExistingChunk: true,
                },
                graphql: {
                    test: /[\\/]node_modules[\\/](graphql|react-apollo|apollo-cache-inmemory|apollo-client|apollo-link|apollo-link-context|apollo-link-http|apollo-link-ws|apollo-utilities)[\\/]/,
                    name: 'apollo',
                    reuseExistingChunk: true,
                },
                form: {
                    test: /[\\/]node_modules[\\/](yup|react-final-form|final-form)[\\/]/,
                    name: 'form',
                    reuseExistingChunk: true,
                    priority: -30,
                },
                vendor: {
                    test: /[\\/]node_modules[\\/](react|react-dom|@lingui|react-router|react-router-dom)[\\/]/,
                    name: 'vendor',
                    reuseExistingChunk: true,
                },
                default: {
                    minChunks: 2,
                    priority: -20,
                    reuseExistingChunk: true,
                },
            },
        },
    };
}

@mschipperheyn Could you please send a PR ?

Should be fixed in dev but not using vendors but chunkGroups config

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mhuggins picture mhuggins  路  3Comments

piersolenski picture piersolenski  路  4Comments

pseudo-su picture pseudo-su  路  3Comments

corydeppen picture corydeppen  路  3Comments

MaxGoh picture MaxGoh  路  4Comments