Skip to content

Metro Guide

kmelmon edited this page Jun 3, 2020 · 32 revisions

Overview

In this article we will cover basics of how the Metro bundler works, how we have customized Metro for react-native-windows, and some internal details to help you find your way around.

Also see: https://github.com/microsoft/react-native-windows/wiki/Metro-Troubleshooting-Guide

The Basics

What is Metro and what are bundles?

Metro is a collection of tightly integrated npm packages, installed along with react-native. See: https://facebook.github.io/metro/.

Metro is a node.js application and is invoked by the react-native CLI.

Metro produces javascript "bundles". What is a bundle? Simply put, a bundle is just a large file containing the javascript code required to run the javascript portion of your react-native application, and assets needed by the javascript (eg images).

Metro and apps work with bundles in several ways:

Working with Metro/bundles during app development
The bundle can be served up by Metro at runtime via an http request. This is the normal scenario during app development. Serving up bundles is part of how fast refresh does its magic - when the javascript changes, Metro produces a patch and sends the patch to the app via another http request. This creates a nice separation between the developer box and the test machine which might be a mobile device. Metro runs an http server listening on localhost:8081 by default, but this can be configured for more advanced scenarios such as remote debugging in a VM (TBD add link).

Of course, for Metro to serve up bundles, you need to first start the Metro server. This can be done a couple ways:
react-native run-windows: This starts Metro and also builds/launches your app
yarn start: This just starts Metro

A typical bundle URL looks something like this:
http://localhost:8081/index.bundle?platform=windows&dev=true&hot=false&inlineSourceMap=true
Here's what the parameters mean:
platform: the native platform to build a bundle for (eg android/ios/windows)
dev: if true, turns on "dev" mode in the javascript code (like Debug mode for native code)
hot: if true, turns on hot reload (?) which we don't actually use in react-native-windows (we use fast refresh)
inlineSourceMap: if true, creates a source map inline with the bundle. A source map is like a pdb file, it tells the debugger where in the original source file a javascript instruction in the bundle corresponds to.
minify: if true, minifies the javascript
TBD: document the rest of the query parameters
Handy debugging trick: You can enter a bundle URL into your browser to test fetching a bundle, if it's successful you'll see the bundle loaded into your browser as plain-text.

Creating offline bundles for a release build
The bundle can be packaged directly into your application as a resource (aka "offline bundle"). In this case Metro produces a bundle as part of building your application. This is the normal scenario for a release build.
A basic bundle command looks something like this:
npx react-native bundle --platform windows --entry-file index.js --bundle-output windows\myAwesomeApp\Bundle\index.windows.bundle --assets-dest windows\myAwesomeApp\Bundle
TBD: document the bundle parameters

In addition a bundle can be loaded into a javascript engine running out-of-process. TBD describe web debugging and also talk about turbo modules and the need to move to direct debugging.

Going in Deeper

Configuring Metro

TBD point to default config, talk about metro.config.js

Customization of metro configuration for react-native-windows

To understand how we customize the metro configuration for react-native-windows, you must first understand an important detail of out-of-tree platforms: platform-specific javascript overrides.

What is an override and how does it work?
react-native-windows is built on top of react-native and shares most of the react-native javascript code. However, some of this code was written with only android/ios in mind and won't work on windows without some changes. There are also cases where windows has functionality that hasn't made it back upstream yet. This is where platform overrides come in. It's expected that the out-of-tree platform may have to override portions of the javascript, this is done simply by making a platform-specific override file for a given javascript file. Metro will use the override in place of the original file when creating bundles. The overridden file has a naming convention of foo.platform.js, where platform is the name of the out-of-tree platform (eg windows). For example, we override Alert.js with Alert.windows.js.

Now for the fun part - getting Metro to pick up the platform override when creating a bundle. There are two parts to this:

  1. When a bundle is being requested, the platform is supplied as a parameter to Metro. This is how Metro knows what overrides to use. Using Alert.js as an example, if the app uses the Alert module, and a bundle is requested with platform = windows, Metro will bundle Alert.windows.js instead of Alert.js.
  2. The override file must be located in the same directory as the file it is overriding. This requirement makes life simpler for the resolver. However this requirement has a very important implication for react-native-windows. The windows overrides don't get published to the react-native npm registry, as they aren't part of react-native. Thus they wont' be present in node_modules/react-native when an app installs react-native. So how do we get windows overrides installed to the same directory? The answer is we publish a copy of all of the react-native javascript along with react-native-windows. To give you an idea of the full ramifications of this, here's a picture of the directory structure for a sample app that has react-native-windows installed to d:\

myAwesomeApp

node_modules

react-native

(all of react-native, including JS, ios/android native code)

react-native-windows

(all of react-native (JS only), plus windows overrides)

As you can see, all of the react-native javascript is actually installed to two locations. When you build a bundle for windows, Metro will pick up all of react-native from within node_modules\react-native-windows (along with the windows overrides), not from within node_modules\react-native. However, if you build a bundle for ios/android, Metro will fallback to the default behavior of picking up react-native from node_modules\react-native. You might be wondering how this magic works! If so read on.

Metro resolver magic
Because of the custom install configuration described above, we changed Metro to magically redirect files that normally live in node_modules\react-native over to node_modules\react-native-windows. Note that this change will work for other out-of-tree platforms as well. The jist of the change: Metro has a central function for resolving every file in a bundle, and this can be overridden. We introduced a custom function to take over the resolving. When resolving files for a given out-of-tree platform, anything that begins with 'react-native' is redirected to that out-of-tree platform. See: https://github.com/react-native-community/cli/pull/1115