stephencookdev / speed-measure-webpack-plugin

⏱ See how fast (or not) your plugins and loaders are, so you can optimise your builds
MIT License
2.42k stars 79 forks source link

speed-measure-webpack-plugin does not work with create-react-app #164

Open danvln opened 3 years ago

danvln commented 3 years ago

Thanks for building this plugin. It would be super useful to make it working with create-react-app, since this has been growing by a large factor, and becomes the de-facto.

Error: Below is the error that I receive in case this is not a known issue:

Users/danv/repos/protos/cra-build/node_modules/neo-async/async.js:16
    throw new Error('Callback was already called.');
    ^

Error: Callback was already called.
    at throwError (/Users/danv/repos/protos/cra-build/node_modules/neo-async/async.js:16:11)
    at /Users/danv/repos/protos/cra-build/node_modules/neo-async/async.js:2819:7
    at processTicksAndRejections (internal/process/task_queues.js:75:11)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! cra-build@0.1.0 start: `node scripts/start.js`
npm ERR! Exit status 1

Project is standard, CRA + Typescript: const smp = new SpeedMeasurePlugin(); const webpackConfigWithMeasure = (webpackEnv) => smp.wrap(webpackConfig(webpackEnv)); module.exports = webpackConfigWithMeasure;

Repro: Note, that the project build, but it crashes at recompile.

floviolleau commented 3 years ago

Hi,

I faced to that same problem few weeks ago. I thought first time it was because I used craco. I finally ejected yesterday, removed craco and still faced to the problem. After some trial and error steps, I discovered it was because I used this plugin.

https://github.com/gsoft-inc/craco/issues/259

For me the problem occured only when doing a yarn run start.

My dependencies' versions:

"@craco/craco": "^6.1.0",
"@types/history": "^4.7.0",
"@types/node": "^14.14.0",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/react-redux": "^5.0.0",
"@types/react-router-dom": "^5.1.0",
"copy-webpack-plugin": "^6.4.0",
"express": "^4.17.0",
"history": "^4.10.0",
"html-webpack-plugin": "^4.5.0",
"react": "^17.0.0",
"react-dev-utils": "^11.0.0",
"react-dom": "^17.0.0",
"react-intl": "^5.12.0",
"react-redux": "^5.1.0",
"react-router-dom": "^5.2.0",
"react-scripts": "^4.0.0",
"typescript": "^4.1.0",
"web-vitals": "^1.1.0"

Thanks!

wctiger commented 3 years ago

Seeing same error when recompiling with react-scripts ^3.4.4 and react-app-rewired.

IhsanMujdeci commented 3 years ago

I got it working with such a configuration

const smp = new SpeedMeasurePlugin();
module.exports ={
  webpack: {
    configure: webpackConfig => (smp.wrap(webpackConfig))
  }
};
silverboyir commented 3 years ago

I got it working with such a configuration

const smp = new SpeedMeasurePlugin();
module.exports ={
  webpack: {
    configure: webpackConfig => (smp.wrap(webpackConfig))
  }
};

how should I use this configuration?

FeiFanLiang commented 3 years ago

I got it working with such a configuration

const smp = new SpeedMeasurePlugin();
module.exports ={
  webpack: {
    configure: webpackConfig => (smp.wrap(webpackConfig))
  }
};

not apply to me,its not work

HarveyZgit commented 3 years ago

I got it working with such a configuration

const smp = new SpeedMeasurePlugin();
module.exports ={
  webpack: {
    configure: webpackConfig => (smp.wrap(webpackConfig))
  }
};

how should I use this configuration?

Use it in config-overrides.js. doc: react-app-rewired/READEME.md

By default, the config-overrides.js file exports a single function to use when customising the webpack configuration for compiling your react app in development or production mode. It is possible to instead export an object from this file that contains up to three fields, each of which is a function.

Ricola3D commented 2 years ago

Issue still present, it does not work with CRA. I narrowed the issue and did a little work-around for now. It doesn't measure all, but it measures mosts of the build.

I added in my config-override.js, after the instanciation of smp:

/**
 * Change SpeedMeasurePlugin's wrap function so it is functional with CRA (and react-scripts
 * @param {*} config
 * @returns
 */
const wrapForCRA = function (config) {
  if (this.options.disable) return config
  if (Array.isArray(config)) return config.map(this.wrap)
  if (typeof config === 'function')
    return (...args) => this.wrap(config(...args))

  config.plugins = (config.plugins || []).map((plugin) => {
    const pluginName =
      Object.keys(this.options.pluginNames || {}).find(
        (pluginName) => plugin === this.options.pluginNames[pluginName]
      ) ||
      (plugin.constructor && plugin.constructor.name) ||
      '(unable to deduce plugin name)'

    // DOES NOT WORK WITH CRA: "URIError: Failed to decode param '/%PUBLIC_URL%/*** */.js'
    // if (plugin instanceof HtmlWebpackPlugin) {
    //   return new WrappedPlugin(plugin, pluginName, this)
    // }
    if (plugin instanceof InlineChunkHtmlPlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    // DOES NOT WORK WITH CRA: "URIError: Failed to decode param '/%PUBLIC_URL%/*** */.js'
    // if (plugin instanceof InterpolateHtmlPlugin) {
    //   return new WrappedPlugin(plugin, pluginName, this)
    // }
    if (plugin instanceof ModuleNotFoundPlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof webpack.DefinePlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof webpack.HotModuleReplacementPlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof CaseSensitivePathsPlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof WatchMissingNodeModulesPlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof MiniCssExtractPlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof ManifestPlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof webpack.IgnorePlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof WorkboxWebpackPlugin.InjectManifest) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof ForkTsCheckerWebpackPlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof ESLintPlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof UglifyJsPlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }

    return plugin
  })

  if (config.optimization && config.optimization.minimizer) {
    config.optimization.minimizer = config.optimization.minimizer.map(
      (plugin) => {
        return new WrappedPlugin(plugin, plugin.constructor.name, this)
      }
    )
  }

  // DOES NOT WORK WITH CRA: "./src/index.css Module build failed: Error: Final loader (./node_modules/speed-measure-webpack-plugin/loader.js) didn't return a Buffer or String"
  // if (config.module && this.options.granularLoaderData) {
  //   config.module = prependLoader(config.module)
  // }

  if (!this.smpPluginAdded) {
    config.plugins = config.plugins.concat(this)
    this.smpPluginAdded = true
  }

  return config
}

smp.wrap = wrapForCRA.bind(smp)

Also you can also integrate "customize-cra" to have a progress bar and a few more details. Here is the full-file. DO NOT FORGET TO INSTALL THE PLUGINS to have it work:

/* eslint-disable no-param-reassign */
/* eslint-disable func-names */
/* eslint-disable import/no-extraneous-dependencies */
/**
 * Recommanded way to overide create-react-app build config without ejecting.
 * We use react-app-rewired to plug the config-overrides.js and export a new webpack config
 * We use customize-cra methods to provide shortcut methods.
 * See doc here: https://v4.mui.com/guides/minimizing-bundle-size/
 */
const webpack = require('webpack')
const {
  addBundleVisualizer,
  addWebpackPlugin,
  disableEsLint,
  override,
  overrideDevServer,
  useBabelRc,
} = require('customize-cra')

const ProgressBarPlugin = require('progress-bar-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
const { WrappedPlugin } = require('speed-measure-webpack-plugin/WrappedPlugin')
const { prependLoader } = require('speed-measure-webpack-plugin/utils')

const PnpWebpackPlugin = require('pnp-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin')
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin')
const TerserPlugin = require('terser-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const ManifestPlugin = require('webpack-manifest-plugin')
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin')
const WorkboxWebpackPlugin = require('workbox-webpack-plugin')
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin')
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
const ESLintPlugin = require('eslint-webpack-plugin')
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin')
const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin')

/**
 * Measure build speed
 * Documentation: https://github.com/stephencookdev/speed-measure-webpack-plugin
 */
const smp = new SpeedMeasurePlugin({
  compareLoadersBuild: {
    filePath: './buildInfo.json',
  },
  disable: !process.env.MEASURE,
  granularLoaderData: true,
  loaderTopFiles: 10,
  outputFormat: 'humanVerbose',
})

/**
 * Change SpeedMeasurePlugin's wrap function so it is functional with CRA (and react-scripts
 * @param {*} config
 * @returns
 */
const wrapForCRA = function (config) {
  if (this.options.disable) return config
  if (Array.isArray(config)) return config.map(this.wrap)
  if (typeof config === 'function')
    return (...args) => this.wrap(config(...args))

  config.plugins = (config.plugins || []).map((plugin) => {
    const pluginName =
      Object.keys(this.options.pluginNames || {}).find(
        (pluginName) => plugin === this.options.pluginNames[pluginName]
      ) ||
      (plugin.constructor && plugin.constructor.name) ||
      '(unable to deduce plugin name)'

    // DOES NOT WORK WITH CRA: "URIError: Failed to decode param '/%PUBLIC_URL%/*** */.js'
    // if (plugin instanceof HtmlWebpackPlugin) {
    //   return new WrappedPlugin(plugin, pluginName, this)
    // }
    if (plugin instanceof InlineChunkHtmlPlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    // DOES NOT WORK WITH CRA: "URIError: Failed to decode param '/%PUBLIC_URL%/*** */.js'
    // if (plugin instanceof InterpolateHtmlPlugin) {
    //   return new WrappedPlugin(plugin, pluginName, this)
    // }
    if (plugin instanceof ModuleNotFoundPlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof webpack.DefinePlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof webpack.HotModuleReplacementPlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof CaseSensitivePathsPlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof WatchMissingNodeModulesPlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof MiniCssExtractPlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof ManifestPlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof webpack.IgnorePlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof WorkboxWebpackPlugin.InjectManifest) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof ForkTsCheckerWebpackPlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof ESLintPlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }
    if (plugin instanceof UglifyJsPlugin) {
      return new WrappedPlugin(plugin, pluginName, this)
    }

    return plugin
  })

  if (config.optimization && config.optimization.minimizer) {
    config.optimization.minimizer = config.optimization.minimizer.map(
      (plugin) => {
        return new WrappedPlugin(plugin, plugin.constructor.name, this)
      }
    )
  }

  // DOES NOT WORK WITH CRA: "./src/index.css Module build failed: Error: Final loader (./node_modules/speed-measure-webpack-plugin/loader.js) didn't return a Buffer or String"
  // if (config.module && this.options.granularLoaderData) {
  //   config.module = prependLoader(config.module)
  // }

  if (!this.smpPluginAdded) {
    config.plugins = config.plugins.concat(this)
    this.smpPluginAdded = true
  }

  return config
}

smp.wrap = wrapForCRA.bind(smp)

/**
 * Exporting the new config for react-app-rewired
 */
module.exports = {
  // The function to use to create a webpack dev server configuration when running the development
  // server with 'npm run start' or 'yarn start'.
  // Example: set the dev server to use a specific certificate in https.
  devServer: overrideDevServer(),
  // The Jest config to use when running your jest tests - note that the normal rewires do not
  // work here.
  jest(config) {
    // ...add your jest config customisation...
    return config
  },
  // The paths config to use when compiling your react app for development or production.
  paths(paths /* , env */) {
    // ...add your paths config
    return paths
  },
  webpack: smp.wrap(
    override(
      // Use .babelrc file. See doc here: https://v4.mui.com/guides/minimizing-bundle-size/
      // eslint-disable-next-line react-hooks/rules-of-hooks
      useBabelRc(),
      // disable eslint in webpack
      disableEsLint(),
      // add webpack bundle visualizer if BUNDLE_VISUALIZE flag is enabled
      process.env.BUNDLE_VISUALIZE === 1 && addBundleVisualizer(),
      // Add a build progress bar
      addWebpackPlugin(new ProgressBarPlugin()),
      // Add uglify plugin
      addWebpackPlugin(
        new UglifyJsPlugin({
          uglifyOptions: {
            compress: {
              drop_console: true,
              drop_debugger: true,
            },
            warning: false,
          },
        })
      )
    )
  ),
}
nektro commented 2 years ago
const smp = new SpeedMeasurePlugin();
module.exports ={
  webpack: {
    configure: webpackConfig => (smp.wrap(webpackConfig))
  }
};

this route is actually what caused the issue for me. what got it working for me was:

const smp = new SpeedMeasurePlugin();
module.exports = {
  webpack: smp.wrap({
    plugins: [
      //...
    ],
  }),
};