react-native: Metro bundler not starting when running run-android / run-ios command on linux
Description
When running the react-native run-android
command it tells me that the JS server (metro bundler) is starting, but in fact it does not and the application in the emulator fails to start as of this issue. Runningreact-native start
separately resolves the issue. I think this issue might apply for run-ios
as well although this needs to be confirmed.
React Native version:
react-native: 0.62.2 @react-native-community/cli: 4.8.0 node: 12 // this version does not matter system: ubuntu 18.04 // this does not matter as well
Steps To Reproduce
Any project with those versions on linux should fail starting the JS server when running react-native run-android
.
Expected Results
- When running
react-native run-android
the bundler should be started if it’s not started already in a separate terminal window. - When running
react-native run-android
and there is an issue with the JS bundler the primary process should display errors why the JS bundler failed starting.
Related issues
https://github.com/facebook/react-native/issues/25509 https://github.com/facebook/react-native/issues/28281
Snack, code example, screenshot, or link to a repository:
I have debugged the issue extensively since none of the related issue provided any solution for my case. I have found several problems with the linux implementation and I will lay them out here. I have also worked out a hacky temporary solution for the fix.
Temporary solution
To hot fix this problem we can leverage the --terminal parameter on run-android
command. This is very hacky but it works
- Add the following in your package.json:
"scripts": {
"android-linux": "react-native run-android --terminal \"$PWD/shgnome\""
}
- Create a new file called
shgnome
the modified terminal runner in your project root with this content:
#!/bin/bash
# @TODO: Remove once issue is fixed in react-native.
set -e
# Remove all params and leave only the path to the script.
script=$(echo "$@" | awk '{print $2}')
gnome-terminal -- "$script"
- Run
chmod u+x ./shgnome
. - Run
npm run android-linux
.
Actual issues
- The first issue is in the function
runAndroid
in file@react-native-community/cli-platform-android/build/runAndroid/index.js
. The callstartServerInNewWindow
return value is completely ignored and thus any potential errors are swallowed. This is why I was not getting any errors in the primary process and this is why this issue survives so long. I suggest that if stderr is set than it should be printed or thrown, but this depends also on the other fixes required.
try {
startServerInNewWindow(args.port, args.terminal, config.reactNativePath);
} catch (error) {
_cliTools().logger.warn(`Failed to automatically start the packager server. Please run "react-native start" manually. Error details: ${error.message}`);
}
- The process invoked for the metro bundler is
react-native/scripts/launchPackager.command
. This file does not have the -e cmd flag and thus it does not exit with proper error code if things fail in it’s process. This was also influencing the 1. issue since if the flag is set then we get proper error message in the primary process (run-android) that the JS bundler failed to start and why it failed.
#!/bin/bash
# Set terminal title
echo -en "\\033]0;Metro\\a"
clear
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
# shellcheck source=/dev/null
. "$THIS_DIR/packager.sh"
if [[ -z "$CI" ]]; then
echo "Process terminated. Press <enter> to close the window"
read -r
fi
- The
startServerInNewWindow
function in@react-native-community
spawns thelaunchPackager.command
process in an sh shell, while in fact thelaunchPackager.command
explicitly says that it require bash. Because that shellscript was written for bash when it runs with sh it fails due to syntax differences. This failure is not printed to the console of the primary process and is not detected because of issue 2. For this I would suggest letting the shellscript decide in what to run itself and just be called directly as the hot-fix shows. But I am not sure why sh was enforced there and so input is required for this. Note that the code below is modified in the catch case when the terminal is not set with my proposed solution. The try block is the old one as you can see has the sh. I would also suggest not doing the fallback as such and rather if --terminal is set and it does not work just throw an error to the user. The current fallback implementation is unnecessary slower in cases where the the --terminal is not set and it’s bad developer experience if the --terminal is set but in fact it doesn’t work.
if (process.platform === 'linux') {
try {
return _execa().default.sync(terminal, ['-e', `sh ${launchPackagerScript}`], { ...procConfig,
detached: true
});
} catch (error) {
// By default, the child shell process will be attached to the parent
return _execa().default(`gnome-terminal -- ${launchPackagerScript}`, {
...procConfig,
shell: true,
detached: true,
stdio: 'pipe'
});
}
}
- There are two flows on linux: 1 if the --terminal is given then the process is opened separately but if it is not it won’t be separate and will be blocking. I am not sure what the implementation was intended there, but even if I applied the fix to change sh to bash it was blocking the primary process and it also wasn’t displaying the JS bundler process`s stdout since it is not implemented as such. My solution for this was to use
gnome-terminal
as can be seen above: this allows to open a separate window and set the blunder to be a separate process.
I would also suggest having tests for the metro bundler auto start.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 17
- Comments: 19 (2 by maintainers)
This is still an issue. Please some contributor prevent this ticket from going stale.
Confirmed the workaround of @archfz, it worked just fine. Or simply using this command in package.json :
still experiencing this issue on 0.66
Up.
Setting
export REACT_TERMINAL='tilix'
and then runningnpx react-native run-android
spawns metro bundler using the chosen terminal automatically for me.Source: https://github.com/react-native-community/cli/blob/master/packages/tools/src/getDefaultUserTerminal.ts#L6