Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Webpack performance plugin in CI will make build fail #671

Closed
annez opened this issue Jun 19, 2018 · 9 comments
Closed

Webpack performance plugin in CI will make build fail #671

annez opened this issue Jun 19, 2018 · 9 comments
Labels

Comments

@annez
Copy link

annez commented Jun 19, 2018

The problem

Currently, Webpack has a performance default of 244KiB (un-gzipped) where if process.env.CI = true is set, the build will fail complaining about degraded performance.

Razzle, without any additions, is already at 192KiB (un-gzipped) excluding any CSS (the above counts CSS as well).

Any reasonable additions to the base Razzle project will basically make the build fail if you are running it in a CI.

entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
  client (248 KiB)
      static/css/bundle.c91c1092.css
      static/js/bundle.5bcffbdf.js

webpack performance recommendations:
You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
For more info visit https://webpack.js.org/guides/code-splitting/

Steps to reproduce

  1. Add some tiny 2.99kB of CSS: https://gist.github.com/annez/ad5c49932358a871eb340c6773b9f4bb
  2. Add some core-js polyfills to browsers don't break - assign, find, from, promise
  3. Add some reasonable React packages - react-autosuggest for example
  4. Run CI=true npm run build and watch it fall down

Recommendations

I think it's confusing to the bundle size error on uncompressed yet we show the compressed size if the build succeeds. Also 244KiB of uncompressed JavaScript has never been an indicator of degraded performance - people should be using performance monitoring and budgeting to understand when something degrades user experience.

Several options here are:

  • Remove webpack.performance entirely from the default Razzle config
  • Set an larger upper bound limit such as 512KiB (although again, arbitrary if we are measuring real performance)
@rhodee
Copy link
Collaborator

rhodee commented Jun 22, 2018

Ran into same issue. Although I think removal seems a bit of a disservice. You can use the razzle.config.js to configure this as you wish. Perhaps you would do it like this:

module.exports = {
  // ...
  modify(defaultConfig, { target, dev }, webpack) {
      if (!dev) {
        config.performance = Object.assign({}, {
          maxAssetSize: 100000,
          maxEntrypointSize: 300000,
          hints: false
        })
      }

    return config
  }
}

@stale
Copy link

stale bot commented Aug 21, 2018

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.

@stale stale bot added the stale label Aug 21, 2018
@stale
Copy link

stale bot commented Aug 28, 2018

ProBot automatically closed this due to inactivity. Holler if this is a mistake, and we'll re-open it.

@stale stale bot closed this as completed Aug 28, 2018
@DevanB
Copy link

DevanB commented Oct 14, 2018

I too have ran into this, and think it should be scrutinized in future versions. I enjoy the gesture of built in performance budgeting, but the 52KiB allowance is rather small.

@mrmartineau
Copy link

I agree, but what I found problematic was that the performance.hints settings triggers a warning (or a full error if on CI) before the files have been fully compressed. In my case running razzle build on CI looks like this:

$ razzle build
Creating an optimized production build...
Compiling client...
Starting type checking and linting service...
Using 1 worker with 2048MB memory limit

Treating warnings as errors because process.env.CI = true.
Most CI servers set it automatically.

Failed to compile.

asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets:
  static/js/bundle.99af500d.js (634 KiB)

entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
  client (634 KiB)
      static/js/bundle.99af500d.js

error Command failed with exit code 1.

And on my local machine, it looks like this:

$ razzle build
Creating an optimized production build...
Compiling client...
Starting type checking and linting service...
Using 1 worker with 2048MB memory limit
Compiled client successfully.
Compiling server...
Compiled server successfully.
Compiled successfully.

File sizes after gzip:

  181.77 KB  build/static/js/bundle.99af500d.js
  10.04 KB   build/static/js/2.945fb472.chunk.js
  4.87 KB    build/static/js/3.c9f99c02.chunk.js
  1.9 KB     build/static/js/6.a430e3d3.chunk.js
  1.74 KB    build/static/js/4.ee533cb7.chunk.js
  1.04 KB    build/static/js/0.aec4785f.chunk.js
  574 B      build/static/js/5.11154537.chunk.js

✨  Done in 10.95s.

You can see that the final build is much smaller than the 244KiB limits set as default by webpack's performance.hints setting.

I have added @rohidee's suggestion to our razzle.config.js, but would much rather a neater way to get around this problem.

FYI, you can test how your CI might build your app but adding env CI=true before your build command, in my case it is: env CI=true yarn build

@bartlangelaan
Copy link
Contributor

I've also experienced what @mrmartineau stated.

I think it's a good thing that there is a default asset size limit; it keeps you sharp about what you include in your entry bundle.

However, I would expect the same chunks to be made with and without the CI environment variable set. I understand that there is a difference between a warning and an error on the CI, but I don't understand why there are more chunks when bulding locally as opposed to building in the CI.

@vspedr
Copy link

vspedr commented May 6, 2019

Can we reopen this issue by any chance? I'm still getting different bundle sizes depending on CI env variable. I tried looking into the default config here but found nothing of interest.

My workaround was to set the CI build script to CI=false yarn build so the resulting bundle is the same that I usally get when running yarn build locally, without having to disable bundle size warnings.

@gobengo
Copy link
Contributor

gobengo commented Aug 15, 2019

I got around this by:

  • Enabling the webpack split chunks plugin to make a 'vendor chunk' for all node_modules dependencies, so they are separate from my application code
  • Disabling webpack performance hints for the vendor chunk (but leaving it enabled for other ones I don't expect to be very big)

In doing this I realized that it's not very reasonable to expect razzle to calculate these hints based on gzip filesize. it's all done by webpack performance hints plugin, which operates on non-gzip.
https://webpack.js.org/configuration/performance/

My razzle.config.js:

const logger = require('razzle-dev-utils/logger')

module.exports = {
  plugins: [
    useRuntimePortEnvironmentVariable,
    WebpackSplitChunksRazzlePlugin({
      // include both initial and async imports for chunking, not just initial
      chunks: 'all',
      // default is 3
      maxInitialRequests: Infinity,
      // default is 30000
      minSize: 0,
    }),
    WebpackPerformanceHintsRazzlePlugin({
      // Use a custom assetFilter to not warn about big source maps or vendor bundle
      assetFilter: function(assetFilename) {
        const isSrcMap = /\.map$/.test(assetFilename)
        const isVendorBundle = /vendor/.test(assetFilename)
        return ! (isSrcMap || isVendorBundle)
      }
    }),
    'typescript',
  ],
}

function NoopRazzlePlugin() {
  return function NoopRazzlePluginFunc(config) {
    return config
  }
}

function WebpackPerformanceHintsRazzlePlugin(pluginOptions) {
  return function WebpackPerformanceHintsRazzlePluginFunc(config) {
    return {
      ...config,
      performance: {
        ...config.performance,
        assetFilter: function(assetFilename) {
          const isSrcMap = /\.map$/.test(assetFilename)
          const isVendorBundle = /vendor/.test(assetFilename)
          return ! (isSrcMap || isVendorBundle)
        }
      }
    }
  }
}

/**
 * Update config to use process.env.PORT provided at *runtime*, not build-time, which is the default behavior
 */
// https://github.com/jaredpalmer/razzle/issues/906#issuecomment-467046269
function useRuntimePortEnvironmentVariable(config, { target, dev }, webpack) {
  const appConfig = Object.assign({}, config);

  // @BUG: Do not inline certain env vars; https://github.com/jaredpalmer/razzle/issues/356
  if (target === 'node') {
    const idx = appConfig.plugins.findIndex(plugin => plugin.constructor.name === 'DefinePlugin');
    const { definitions } = appConfig.plugins[idx];
    const newDefs = Object.assign({}, definitions);
    
    delete newDefs['process.env.PORT'];
    delete newDefs['process.env.HOST'];
    delete newDefs['process.env.PUBLIC_PATH'];

    appConfig.plugins = [].concat(appConfig.plugins);
    appConfig.plugins[idx] = new webpack.DefinePlugin(newDefs)
  }

  return appConfig;
}

/**
 * Razzle Plugin to split common libraries into a chunk named 'vendor'.
 * The idea is that this bundle will change way less often than the src code
 * of this app, so will mean less average download size because vendor can be cached.
 * Taken from https://github.com/jaredpalmer/razzle/tree/master/examples/with-vendor-bundle
 */
function WebpackSplitChunksRazzlePlugin(pluginOptions={}) {
  return function WebpackSplitChunksRazzlePluginFunc (razzleConfigBefore, { target, dev }, webpack) {
    const config = Object.assign({}, razzleConfigBefore);

    // Change the name of the server output file in production
    if (target === 'web') {
      // modify filenaming to account for multiple entry files
      config.output.filename = dev
        ? 'static/js/[name].js'
        : 'static/js/[name].[hash:8].js';

      // I think these are the default that webpack sets
      // https://webpack.js.org/plugins/split-chunks-plugin/#optimizationsplitchunks
      const defaultSplitChunksConfig = {
        chunks: 'async',
        minSize: 30000,
        maxSize: 0,
        minChunks: 1,
        maxAsyncRequests: 5,
        maxInitialRequests: 3,
        automaticNameDelimiter: '~',
        automaticNameMaxLength: 30,
        name: true,
        cacheGroups: {
          vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10
          },
          default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true
          }
        }
      }
      config.optimization.splitChunks = {
        ...defaultSplitChunksConfig,
        ...config.optimization.splitChunks,
        ...pluginOptions,
      }
    }

    return config;
  }
}

ek68794998 added a commit to ek68794998/covid-employer-response that referenced this issue Apr 11, 2020
ek68794998 added a commit to ek68794998/covid-employer-response that referenced this issue Apr 11, 2020
* Add build step to CI action

* Update Razzle asset limit to 400kB

Based on: jaredpalmer/razzle#671
@wescopeland
Copy link

wescopeland commented Jun 11, 2020

A warning to anyone who stumbles here: for whatever reason, the solution proposed above with useRuntimePortEnvironmentVariable, WebpackSplitChunksRazzlePlugin, and WebpackPerformanceHintsRazzlePlugin will work great in development mode but can break client-side rendering in production mode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

8 participants