metro icon indicating copy to clipboard operation
metro copied to clipboard

Slow bundle download(~2 mins) in dev mode by ReloadCommand(r) on iOS after RN 72 upgrade

Open jayshah123 opened this issue 11 months ago • 13 comments

Description

I have a large bundle. After recent upgrade to RN 0.72.12, metro version - [email protected] (Also tried with RN 0.73.9 and Metro v0.80.8), I see large download times for already traversed bundle, when using reload command "r"

The first load, bundle does not need downloading.

But for every reload command("r" press on iOS), the bundle download to simulator (for the already traversed bundle) takes about 2 mins.

I did some analysis on size of the bundle transferred by Metro Server is ~248 MB. I am working to reduce bundle size, but due to lack of tree-shaking, the current size is about ~248 MB. In the absensece of tree shaking, what tools are recommended for bundle size analysis?

The size was measured by the value of following code in Server.js in metro source code.

mres.setHeader(
          "Content-Length",
          String(Buffer.byteLength(result.bundle))
        );

For the same project, if I try to download using curl, it takes about 7s to download to a file, for an already traversed bundle, which was measured via:

curl -kv -w '\n* Response time: %{time_total}s\n' http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&inlineSourceMap=false&modulesOnly=false&runModule=true&app=com.mypackagename

Steps to reproduce

  1. install the application which has a really large bundle
  2. let the bundle load completely.
  3. Press "r" to reload.
  4. You will see Downloading "1..100%" takes around 2 mins to finish.

React Native Version

0.72.12

Affected Platforms

Runtime - iOS

Output of npx react-native info

System:
  OS: macOS 14.3.1
  CPU: (10) arm64 Apple M1 Max
  Memory: 7.24 GB / 32.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 18.13.0
    path: ~/.nvm/versions/node/v18.13.0/bin/node
  Yarn:
    version: 1.18.0
    path: ~/.nvm/versions/node/v18.13.0/bin/yarn
  npm:
    version: 8.19.3
    path: ~/.nvm/versions/node/v18.13.0/bin/npm
  Watchman:
    version: 2024.01.22.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.14.3
    path: /Users/jshah/.rbenv/shims/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 23.2
      - iOS 17.2
      - macOS 14.2
      - tvOS 17.2
      - visionOS 1.0
      - watchOS 10.2
  Android SDK: Not Found
IDEs:
  Android Studio: 2022.3 AI-223.8836.35.2231.10811636
  Xcode:
    version: 15.2/15C500b
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.10
    path: /usr/bin/javac
  Ruby:
    version: 3.3.0
    path: /Users/jshah/.rbenv/shims/ruby
npmPackages:
  "@react-native-community/cli": Not Found
  react:
    installed: 18.2.0
    wanted: 18.2.0
  react-native:
    installed: 0.72.12
    wanted: 0.72.12
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: false
iOS:
  hermesEnabled: true
  newArchEnabled: false

Stacktrace or Logs

NA

Reproducer

private code/bundle.

Screenshots and Videos

Downloading 1..100% takes ~2 mins when ReloadCommand "r" is pressed.

Screenshot 2024-03-23 at 10 15 53 AM

jayshah123 avatar Mar 23 '24 02:03 jayshah123

:warning: Newer Version of React Native is Available!
:information_source: You are on a supported minor version, but it looks like there's a newer patch available - 0.72.12. Please upgrade to the highest patch for your minor or latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If it does not repro, please let us know so we can close out this issue. This helps us ensure we are looking at issues that still exist in the most recent releases.

github-actions[bot] avatar Mar 23 '24 02:03 github-actions[bot]

:warning: Missing Reproducible Example
:information_source: We could not detect a reproducible example in your issue report. Please provide either:
  • If your bug is UI related: a Snack
  • If your bug is build/update related: use our Reproducer Template. A reproducer needs to be in a GitHub repository under your username.

github-actions[bot] avatar Mar 23 '24 02:03 github-actions[bot]

Issue still persists when updated to 0.72.12.

jayshah123 avatar Mar 23 '24 02:03 jayshah123

⚠️ Newer Version of React Native is Available! ℹ️ You are on a supported minor version, but it looks like there's a newer patch available - 0.72.12. Please upgrade to the highest patch for your minor or latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If it does not repro, please let us know so we can close out this issue. This helps us ensure we are looking at issues that still exist in the most recent releases.

Tried this, still the same issue.

Let me know any relevant files/code/functions which can be investigated. Are there ways to speed up RCTMultipartDataTask or find bottlenecks between simulator<->metro server?

jayshah123 avatar Mar 23 '24 04:03 jayshah123

@jayshah123 you could try react-native-bundle-visualizer

efstathiosntonas avatar Mar 25 '24 20:03 efstathiosntonas

Bundle size does not seem to be a problem, due to following observation:

Good case - kill the app by swipe from recents, relaunch from apps - Downloading (an already traversed bundle) takes ~5 secs.

On the other hand, Bad case - "r key press" - Downloading (an already traversed bundle) takes ~2 mins.

There seems to be no difference on the server sides for both cases - returns instantly from metro server ~1s in the finish section of requestprocessor.

The only difference I notice between good vs bad case is lots of slow reads on RCTMultipartStreamReader's readAllPartsWithCompletionCallback method doing the stream reads are significantly slower: NSInteger bytesRead = [_stream read:buffer maxLength:bufferLen]; Where I see:

  1. Good case - 0.000006 seconds per read -> leading to 5s download time.
  2. Bad case - 0.002 seconds per read -> leading to 2 mins download time.

Maintainers, what is a good place/way to start investigating the root cause? Happy to raise a fix PR in here or in RN depending on the root cause and fix. cc @robhogan for any suggestions.

jayshah123 avatar Mar 30 '24 03:03 jayshah123

@jayshah123 I'm having the same problem on my local network when connecting to Metro from Pixel 4 or iPhone 15 plus through an Xfinity gateway. The IP address of my 2023 MacBook Pro is a little different, 10.0.0.113. But that shouldn't matter. Tried turning off Private Relay on both Apple devices. Tried internet sharing between Apple devices.

It's 54 MB bundle that normally takes 72 seconds to load. Turning off my work VPN on the MacBook helped get it down to 30 seconds. But that's still way too long :( .

509dave16 avatar Jul 14 '24 03:07 509dave16

I face the same problem. http://192.168.1.121:8081/index.bundle?platform=ios&dev=true&lazy=true&minify=false&inlineSourceMap=false&modulesOnly=false&runModule=true&app=space.zhumi.app I type the url in my pc chrome, and it keeped a loading continuous text output status. it takes half an hour. image

my metro config file.

`const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');

const defaultConfig = getDefaultConfig(__dirname);

const { resolver: { sourceExts, assetExts }, } = getDefaultConfig(__dirname);

const config = { transformer: { getTransformOptions: async () => ({ transform: { experimentalImportSupport: false, inlineRequires: true, }, }), babelTransformerPath: require.resolve('react-native-svg-transformer'), }, resolver: { assetExts: assetExts.filter(ext => ext !== 'svg'), sourceExts: [...sourceExts, 'svg'], }, };

module.exports = mergeConfig(defaultConfig, config);`

21aeda86a65dfeb182811cee066047f

zhangshaoju1987 avatar Sep 03 '24 14:09 zhangshaoju1987

Now on RN version 0.73.9, same issue persists.

jayshah123 avatar Oct 01 '24 12:10 jayshah123

Now on RN version 0.73.9, same issue persists.

Same here

montaserfzy avatar Oct 02 '24 10:10 montaserfzy

Could someone make a screen recording of the output of Metro during this slow request, starting Metro with DEBUG=Metro:* yarn start to show debug output?

Bonus; the debug output should show the bundle URL being requested by the app. Repeat the test using curl to get download the bundle and compare the speed. If it’s fast with curl, it’s an RN rather than Metro issue.

Edit: looks like there are at least two separate issues here - @jayshah123’s investigation seems to rule out a server issue and narrows it down to RN iOS.

@zhangshaoju1987 is experiencing slow downloads even to Chrome, so that looks like slow bundling on the Metro side, but we’d need more detail - please open a separate issue for slow bundling.

robhogan avatar Oct 02 '24 10:10 robhogan

As an additional information, I am attaching both metro config and recording for metro loading + curl fetch.

Here is my Metro config:

/**
 * Metro configuration for React Native
 * https://github.com/facebook/react-native
 *
 * @format
 */
const { getDefaultConfig } = require('@expo/metro-config');

const path = require('path');

async function getConfig(appDir) {
  const config = await getDefaultConfig(__dirname);
  // setup watch folders
  config.watchFolders = [path.resolve(appDir, 'node_modules')];
  // setup transformer
  config.transformer = {
    ...config.transformer,
    babelTransformerPath: require.resolve('react-native-svg-transformer/expo'),
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: true,
        inlineRequires: true,
      },
    }),
  };

  // setup resolver source extensions
  const originalSourceExts = config.resolver.sourceExts;
  let newSourceExts = process.env.RN_SRC_EXT
    ? process.env.RN_SRC_EXT.split(',').concat(originalSourceExts)
    : originalSourceExts;
  config.resolver.sourceExts = [...newSourceExts, 'svg', 'cjs', 'mjs'];

  // setup resolver asset extensions
  const origianlAssetExts = config.resolver.assetExts;
  config.resolver.assetExts = [
    ...origianlAssetExts.filter((ext) => ext !== 'svg'),
    'css',
    'scss',
  ];

  // setup resolver extra node modules
  config.resolver.extraNodeModules = {
    stream: require.resolve('readable-stream'),
  };

  return config;
}

module.exports = getConfig(__dirname);

And here is the video for metro loading: Notice following:

  1. First load time : bundling + downloading is slow
  2. Shake > Reload - Downloading time slow on iOS.
  3. In the end I curl the same bundles from metro server - really fast.

https://github.com/user-attachments/assets/dfc1ca07-7a60-4017-ad71-0660d72cc818

Additionally seeking pointers on following:

  1. speeding up first bundles (inlineRequires is already true - see metro config above)
  2. Tooling for analyzing bundle sizes and how it affects first loads?
  3. Do dynamic imports help with any of this, if yes, then how?

jayshah123 avatar Oct 02 '24 17:10 jayshah123

Let me know if any additional information is needed.

jayshah123 avatar Oct 08 '24 05:10 jayshah123