metro: Metro 0.45 / RN 0.57.0 - Initial builds 5-10x slower

Do you want to request a feature or report a bug? Bug (ported from RN repo as the issue reported by @Winglonelion was closed and locked there - https://github.com/facebook/react-native/issues/21138)

What is the current behavior? First build of react-native project takes ~3 mins (i.e. after running yarn start then react-native run-android). Subsequent builds via Dev Menu -> Reload seem fine although maybe slower speed is less noticeable for incremental builds.

If the current behavior is a bug, please provide the steps to reproduce and a minimal repository on GitHub that we can yarn install and yarn test. Itโ€™s probably not so noticable with small projects so bit hard to do a repro as our production app is not open-source. But building any project with RN 0.56.1 and then 0.57.0 should show a difference.

What is the expected behavior? It used to take maybe ~30s at most for a reasonably sized project. I hardly used to notice the short wait.

Please provide your exact Metro configuration and mention your Metro, node, yarn/npm version and operating system.

metro@0.45.2
  React Native Environment Info:
    System:
      OS: Linux 4.15 Ubuntu 16.04.5 LTS (Xenial Xerus)
      CPU: x64 Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz
      Memory: 753.61 MB / 15.54 GB
      Shell: 4.3.48 - /bin/bash
    Binaries:
      Node: 8.11.0 - ~/.nvm/versions/node/v8.11.0/bin/node
      Yarn: 1.9.4 - /usr/bin/yarn
      npm: 5.6.0 - ~/.nvm/versions/node/v8.11.0/bin/npm
      Watchman: 4.9.0 - /usr/local/bin/watchman
    SDKs:
      Android SDK:
        Build Tools: 23.0.1, 26.0.1, 26.0.2, 26.0.3, 27.0.0, 27.0.3
        API Levels: 16, 19, 22, 23, 26, 27, 28
    IDEs:
      Android Studio: 3.1 AI-173.4907809
    npmPackages:
      react: ^16.5.0 => 16.5.1 
      react-native: 0.57.0 => 0.57.0 
    npmGlobalPackages:
      create-react-native-app: 1.0.0
      react-native-cli: 2.0.1
      react-native-git-upgrade: 0.2.7

NB Might be worth mentioning that in our setup we run in dev mode with yarn metro which is just a script in package.json: node node_modules/react-native/local-cli/cli.js start --reset-cache then yarn android which is just a bash script along the lines of:

adb_all reverse tcp:3000 tcp:3000
# adb_all reverse ...
react-native run-android --appIdSuffix=debug

Where adb_all is a function that loops through all connected devices / emulators and runs an adb command, in this case to reverse proxy our locally running services to the device.

The differences between our scripts and yarn start + react-native run-android for the purposes of this bug report should be pretty minimal though.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 4
  • Comments: 16 (8 by maintainers)

Commits related to this issue

Most upvoted comments

Until we have a fix you can workaround the issue by adding the --max-workers param to the CLI command, eg:

$ node node_modules/react-native/local-cli/cli.js start --reset-cache --max-workers=6

@rafeca ah no way! I had actually noticed my PC was running really quiet (smallish form factor with super noisy fan makes it very noticeable when itโ€™s working hard) My colleagues have probably been enjoying the break from it ๐Ÿ˜‚

Thank you for spending time looking into this one, much appreciated ๐Ÿ˜ƒ

Edit: Yeah looks good: screenshot from 2018-09-18 15-08-11

Also, regarding the cache issues that you mention, these are usually caused by external files that affect the transformation output (like .babelrc or babel.config.js files). Since metro does not know about them, even if you change them and restart metro/etc the cache wonโ€™t get invalidated.

In order to fix that, Metro has a config param called cacheVersion (more info).

You can set it to any string and the whole transform cache of metro will get invalidated once this string changes.

So, in order to use this to invalidate the cache once some files change in your repo change you can do something like:

const crypto = require('crypto');

function getFileHash(file) {
  return crypto
    .createHash('sha1')
    .update(fs.readFileSync(file))
    .digest('hex');
}

const cacheKeyParts = [
  './package.json',
  './yarn.lock',
  './.babelrc',
  /* ... */
].map(file => getFileHash(require.resolve(file)));

const cacheVersion = crypto
    .createHash('sha1')
    .update(cacheKeyParts.join('$'))
    .digest('hex');

This will improve the developer experience quite a lot if your app is big, since transforming the whole app from scratch every time is painful. On production, I guess that is fine to keep the --reset-cache for safety.

Ok! found it! ๐Ÿ˜…

Somehow, when running the development server, the React Native CLI is not configuring Metro to use multiple workers to parallelize the transformation of files across CPUs.

This is why build times is ~5-10X slower (depending on your number of cpus).

Iโ€™m going to send a fix and release a patch version of Metro