Skip to content

Project created with the Metro bundler has no metro.config.js and cannot bundle #727

Description

@htutwaiphyoe

Summary

When a project is created with the Metro bundler, the scaffold does not generate a metro.config.js. Without that file, the dev server cannot build a bundle. Every bundle request returns 404 with Unable to resolve module ./index, even though index.js is present in the project root.

Creating the file by hand fixes the problem, and nothing else needs to change.

For comparison, creating a project with the Re.Pack bundler does generate an rspack.config.mjs, and that project bundles correctly on the first try.

Environment

Package Version
rock, @rock-js/plugin-metro 0.15.1
react-native 0.86.0
@react-native/metro-config 0.86.0
metro (dev server) 0.85.0
Node 24.16.0
OS macOS, arm64

Steps to reproduce

  1. Create a project with the Metro bundler, so that rock.config.mjs uses bundler: pluginMetro(). The project root has no metro.config.js.
  2. Run npx rock start --reset-cache.
  3. Request a bundle:
curl -o /dev/null -w '%{http_code}' 'http://localhost:8081/index.bundle?platform=ios&dev=true'

The server responds with 404 and this body:

{
  "type": "UnableToResolveError",
  "originModulePath": "<project>/.",
  "targetModuleName": "./index",
  "message": "Unable to resolve module ./index from <project>/.: None of these files exist:\n  * index(.ios.js|.native.js|.js|.ios.jsx|.native.jsx|.jsx|...)"
}

The app shows the same message as a red screen on the simulator. The native build, install and launch all succeed, so the failure looks like a problem with the entry file rather than with the configuration.

index.js exists in the project root and has not been modified. Building through the Metro API directly gives a clearer message:

Failed to get the SHA-1 for: <project>/index.js
  1) The file is not watched. Ensure it is under the configured `projectRoot` or `watchFolders`.
  1. Add a standard configuration file and change nothing else:
const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
module.exports = mergeConfig(getDefaultConfig(__dirname), {});
  1. Restart the dev server. The same request now returns 200 and the app runs.

The lockfile and node_modules were identical in both cases, and no .watchmanconfig was added. I also ruled out a stale Metro cache, an incorrect watchman root, symlinks in the project path, and duplicate metro versions in the dependency tree. Both 0.84.6 and 0.85.0 are installed in the working case as well, so that is not the cause.

Possible cause

Running without a metro.config.js looks like it is meant to be supported, since loadMetroConfig.ts logs "No metro.config.js found, using default configuration". However, the configuration returned in that case seems to be missing the plugin's own defaults.

From packages/plugin-metro/src/lib/start/loadMetroConfig.ts on main:

105:  const defaultConfig = RNMetroConfig.getDefaultConfig(ctx.root);
...
114:    RNMetroConfig.setFrameworkDefaults(
115:      getCommunityCliDefaultConfig(ctx, defaultConfig),
116:    );
...
122:  if (projectConfig.isEmpty) {
126:      return defaultConfig;

defaultConfig is created on line 105, before setFrameworkDefaults() is called on line 114. setFrameworkDefaults() affects later calls to getDefaultConfig(), so it does not modify the object that already exists. As a result, the configuration returned on line 126 never receives the values from getCommunityCliDefaultConfig, such as resolver.platforms and serializer.getModulesRunBeforeMainModule.

The branch for React Native below 0.81 does merge those values:

129:      const overrideConfig = getCommunityCliDefaultConfig(ctx, defaultConfig);
130:      return mergeConfig(defaultConfig, overrideConfig);

This also explains why adding a metro.config.js helps. In that case the project's own call to getDefaultConfig(__dirname) runs after setFrameworkDefaults(), so the defaults are applied.

Suggested fix

Generate a metro.config.js when a project is created with the Metro bundler, in the same way that rspack.config.mjs is generated for Re.Pack. The only metro.config.js I could find in this repository is under packages/platform-harmony/template.

It would also help to fix the projectConfig.isEmpty branch, either by calling RNMetroConfig.getDefaultConfig(ctx.root) again after setFrameworkDefaults(), or by merging the overrides in the same way as the branch below it. Otherwise anyone who removes the file will hit the same error.

Notes

  • Switching the project to bundler: pluginRepack() was a working workaround, because that path does not use this code.
  • I confirmed that the bundle fails without the configuration file and succeeds with it, and I read the code above. I did not verify which specific missing default causes the resolution to fail.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions