facebook / metro

🚇 The JavaScript bundler for React Native
https://metrobundler.dev
MIT License
5.23k stars 625 forks source link

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

Open jayshah123 opened 7 months ago

jayshah123 commented 7 months ago

Description

I have a large bundle. After recent upgrade to RN 0.72.12, metro version - metro@0.76.9 (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
github-actions[bot] commented 7 months ago
: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] commented 7 months ago
: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.
jayshah123 commented 7 months ago

Issue still persists when updated to 0.72.12.

jayshah123 commented 7 months ago

⚠️ 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?

efstathiosntonas commented 7 months ago

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

jayshah123 commented 7 months ago

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.

509dave16 commented 4 months ago

@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 :( .

zhangshaoju1987 commented 2 months ago

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

jayshah123 commented 1 month ago

Now on RN version 0.73.9, same issue persists.

montaserfzy commented 1 month ago

Now on RN version 0.73.9, same issue persists.

Same here

robhogan commented 1 month ago

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.

jayshah123 commented 1 month ago

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 commented 1 month ago

Let me know if any additional information is needed.