microsoft / react-native-windows

A framework for building native Windows apps with React.
https://microsoft.github.io/react-native-windows/
Other
16.35k stars 1.14k forks source link

Saving any change to a native module in Visual Studio kills the React Native CLI in Visual Studio Code #13890

Closed shirakaba closed 2 weeks ago

shirakaba commented 1 month ago

Problem Description

I am running the native app via Visual Studio whilst running the React Native CLI in a terminal in Visual Studio Code.

Whenever I save changes to a header file in a native module via Visual Studio, it crashes the React Native CLI running in Visual Studio Code.

node:internal/fs/watchers:255
    throw error;
    ^

Error: EPERM: operation not permitted, watch 'C:\Users\jbizz\git\banana_native\apps\banana\windows\.vs\banana\FileContentIndex\merges'
    at FSWatcher.<computed> (node:internal/fs/watchers:247:19)
    at Object.watch (node:fs:2491:36)
    at NodeWatcher._watchdir (C:\Users\jbizz\git\banana_native\node_modules\metro-file-map\src\watchers\NodeWatcher.js:89:24)
    at C:\Users\jbizz\git\banana_native\node_modules\metro-file-map\src\watchers\NodeWatcher.js:185:22
    at Walker.<anonymous> (C:\Users\jbizz\git\banana_native\node_modules\metro-file-map\src\watchers\common.js:81:31)
    at Walker.emit (node:events:519:28)
    at C:\Users\jbizz\git\banana_native\node_modules\walker\lib\walker.js:69:16
    at FSReqCallback.oncomplete (node:fs:187:23) {
  errno: -4048,
  syscall: 'watch',
  code: 'EPERM',
  path: 'C:\\Users\\jbizz\\git\\banana_native\\apps\\banana\\windows\\.vs\\banana\\FileContentIndex\\merges',
  filename: 'C:\\Users\\jbizz\\git\\banana_native\\apps\\banana\\windows\\.vs\\banana\\FileContentIndex\\merges'
}

Node.js v20.17.0
 ELIFECYCLE  Command failed with exit code 1.

Steps To Reproduce

  1. Create a React Native Windows app (I am using RNW v0.73 with Fabric disabled).
  2. Add any native module to the project.
  3. Start the React Native CLI in Visual Studio Code via the terminal (I am using Git Bash, which is based on MinGW).
  4. Run the RNW app in Visual Studio.
  5. Inside Visual Studio, select the native module and save a change to any source file from that native module, such as ReactNativeModule.h.
  6. The React Native CLI will crash.

If it doesn't reproduce, try having ReactNativeModule.h open at the same time in Visual Studio Code.

Expected Results

The React Native CLI shouldn't crash.

CLI version

12.3.6

Environment

info Fetching system and libraries information...
System:
  OS: Windows 11 10.0.22631
  CPU: (12) x64 12th Gen Intel(R) Core(TM) i5-1235U
  Memory: 1.71 GB / 15.68 GB
Binaries:
  Node:
    version: 20.17.0
    path: ~\AppData\Local\Volta\tools\image\node\20.17.0\node.EXE
  Yarn: Not Found
  npm:
    version: 10.8.2
    path: ~\AppData\Local\Volta\tools\image\node\20.17.0\npm.CMD
  Watchman: Not Found
SDKs:
  Android SDK: Not Found
  Windows SDK:
    AllowDevelopmentWithoutDevLicense: Enabled
    Versions:
      - 10.0.19041.0
      - 10.0.22621.0
      - 10.0.26100.0
IDEs:
  Android Studio: Not Found
  Visual Studio:
    - 17.11.35303.130 (Visual Studio Community 2022)
Languages:
  Java: Not Found
  Ruby: Not Found
npmPackages:
  "@react-native-community/cli": Not Found
  react: Not Found
  react-native: Not Found
  react-native-windows: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: false
iOS:
  hermesEnabled: Not found
  newArchEnabled: Not found

info React Native v0.75.3 is now available (your project is running on v0.73.9).
info Changelog: https://github.com/facebook/react-native/releases/tag/v0.75.3
info Diff: https://react-native-community.github.io/upgrade-helper/?from=0.75.3
info For more info, check out "https://reactnative.dev/docs/upgrading?os=windows".

Community Modules

Custom module created with:

npx react-native@latest init-windows --logging --overwrite --template old/uwp-cpp-lib --name MyLibrary

Target Platform Version

10.0.19041

Target Device(s)

Desktop

Visual Studio Version

Visual Studio 2022

Build Configuration

Debug

Snack, code example, screenshot, or link to a repository

No response

chrisglein commented 1 month ago

The RNW metro config is supposed to be set up to avoid this set of problem of monitoring VS files. In case something wasn't applied correctly, can you share your metro config? Make sure to add these files to the block list in metro.

shirakaba commented 1 month ago

Ah, that might be the place to check!

It doesn't reproduce all the time - some days it's always doing it, and others it doesn't, so I'll have to check next time I'm in the error state.

I have a custom metro.config.js that adds some limited support for Expo (such that you can run expo start and the Metro bundler started up by that happily works together with a RNW app). It may be familiar from #13534, but since then I have incorporated @rnx-kit/metro-config@1.3.15. Some of it may be redundant as a result, I just haven't have time to remove those redundant parts.

Here it is:

const path = require("node:path");
const { makeMetroConfig } = require("@rnx-kit/metro-config");
const { getDefaultConfig } = require("expo/metro-config");

const monorepoRoot = path.resolve(__dirname, "../..");
const reactNativeAppAuthJs = path.resolve(
  monorepoRoot,
  "packages/react-native-app-auth-js",
);

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

/** @type {import("metro-config").MetroConfig} */
const config = {
  ...defaultConfig,
  watchFolders: [
    ...defaultConfig.watchFolders,
    reactNativeAppAuthJs,
    // We add monorepoRoot to fix the following error which occurs with
    // `react-native start` but not `expo start`:
    // > Unable to resolve module @babel/runtime/helpers/interopRequireDefault
    // It occurs because even though Metro traverses the monorepo root's
    // node_modules, it seems to ignore the contents if it's not a watch folder.
    // https://github.com/facebook/react-native/issues/27712#issuecomment-715780864
    monorepoRoot,
  ],
  resolver: {
    ...defaultConfig.resolver,
    assetExts: assetExts.filter((ext) => ext !== "svg"),
    sourceExts: [...sourceExts, "svg"],
    resolveRequest: reactNativePlatformResolver({
      macos: "react-native-macos",
      windows: "react-native-windows",
    }),
    extraNodeModules: {
      ...defaultConfig.resolver.extraNodeModules,
      "@scoville/react-native-app-auth-js": reactNativeAppAuthJs,
    },
  },
  serializer: {
    ...defaultConfig.serializer,
    getModulesRunBeforeMainModule() {
      return [
        require.resolve("react-native/Libraries/Core/InitializeCore"),
        require.resolve("react-native-macos/Libraries/Core/InitializeCore"),
        require.resolve("react-native-windows/Libraries/Core/InitializeCore"),
        ...defaultConfig.serializer.getModulesRunBeforeMainModule(),
      ];
    },
  },
  transformer: {
    ...defaultConfig.transformer,
    babelTransformerPath: require.resolve("react-native-svg-transformer"),
  },
};

module.exports = makeMetroConfig(config);

/**
 * This implementation is inlined from
 * `@react-native/community-cli-plugin/dist/utils/metroPlatformResolver.js`,
 * because it can't be imported directly (the ./utils subpath is not allowed by
 * the package's exports map).
 *
 * This is an implementation of a metro resolveRequest option which will remap
 * react-native imports to different npm packages based on the platform
 * requested. This allows a single metro instance/config to produce bundles for
 * multiple out-of-tree platforms at a time.
 *
 * @param platformImplementations
 * A map of platform to npm package that implements that platform
 *
 * Ex:
 * {
 *    windows: 'react-native-windows',
 *    macos: 'react-native-macos'
 * }
 */
function reactNativePlatformResolver(platformImplementations) {
  return (context, moduleName, platform) => {
    let modifiedModuleName = moduleName;
    if (platform != null && platformImplementations[platform]) {
      if (moduleName === "react-native") {
        modifiedModuleName = platformImplementations[platform];
      } else if (moduleName.startsWith("react-native/")) {
        modifiedModuleName = `${
          platformImplementations[platform]
        }/${modifiedModuleName.slice("react-native/".length)}`;
      }
    }
    return context.resolveRequest(context, modifiedModuleName, platform);
  };
}

I'm happy to either close this issue (and reopen it if I have problems again; or report back if the suggested issue solves it), or leave it open until I get a reproducing case to confirm a fix with. I don't mind either way!

jonthysell commented 1 month ago

RNW uses the following to make sure Metro ignores VS files (from https://github.com/microsoft/react-native-windows/blob/dde3461cb81c738d3b937af2a757617cd43e8742/vnext/templates/cpp-app/metro.config.js#L28C1-L37C8):

blockList: exclusionList([
      // This stops "react-native run-windows" from causing the metro server to crash if its already running
      new RegExp(
        `${path.resolve(__dirname, 'windows').replace(/[/\\]/g, '/')}.*`,
      ),
      // This prevents "react-native run-windows" from hitting: EBUSY: resource busy or locked, open msbuild.ProjectImports.zip or other files produced by msbuild
      new RegExp(`${rnwPath}/build/.*`),
      new RegExp(`${rnwPath}/target/.*`),
      /.*\.ProjectImports\.zip/,
    ]),
mataide commented 1 month ago

@shirakaba Could you publish to us your update version of metro.config.js?

I am having the same problem.

microsoft-github-policy-service[bot] commented 3 weeks ago

This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 7 days. It will be closed if no further activity occurs within 7 days of this comment.