metro: `SHA-1 for file ... is not computed` when using `resolver.resolveRequest`

Do you want to request a feature or report a bug?

Bug

What is the current behavior?

I am using a custom resolver.resolveRequest function in rn-cli.config.js. See https://facebook.github.io/metro/docs/en/configuration#resolver-options.

The error message is being printed from metro/src/node-haste/DependencyGraph.js#getSha1, which is being called by metro/src/Bundler.js#transformFile:

metro/src/node-haste/DependencyGraph.js#getSha1

  getSha1(filename: string): string {
    const resolvedPath = fs.realpathSync(filename);
    const sha1 = this._hasteFS.getSha1(resolvedPath);
    if (!sha1) {
      throw new ReferenceError(`SHA-1 for file ${filename} is not computed`);
    }
    return sha1;
  }

The issue is that the file won’t exist in the haste map if you are resolving it in the resolveRequest hook.

If the current behavior is a bug, please provide the steps to reproduce and a minimal repository on GitHub that we can yarn install and yarn test.

https://github.com/vjpr/expo-v2-test-pnpm/tree/broken

You need to run pnpm install (the reason for the custom resolver is because symlinks are not supported by metro and hence cause problems for pnpm).

What is the expected behavior?

getSha1 should compute hash if not in haste map.

Please provide your exact Metro configuration and mention your Metro, node, yarn/npm version and operating system.

metro@0.45.6 node@8.9.0 pnpm@2.21.1

Workaround

Add to metro/src/node-haste/DependencyGraph.js#getSha1:

    if (!sha1) {
      return getFileHash(resolvedPath)
      function getFileHash(file) {
        return require('crypto')
          .createHash('sha1')
          .update(fs.readFileSync(file))
          .digest('hex')
      }
    }

I have released a patched version here: Use metro-pnpm@0.45.6-vjpr.2

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 107
  • Comments: 78

Commits related to this issue

Most upvoted comments

I solved this issue by using yarn start instead of starting project with react-native start(it uses npm start). I have been adding dependencies in project using Yarn add {dependencyName} and starting project via npm. which created error of SHA-1.

I fixed it by install correct react-native-cli:

yarn global remove react-native
yarn global add react-native-cli

0.61.5 indeed works. I also did npm i -g react-native-cli - it updated cli to 2.0.1 version and bundling worked.

I make it work for this: npx react-native bundle --platform ... but not work when I use like this: react-native bundle --platform

didn’t know why!

Just an update on why this happens.

Here is the function that is generating the message:

https://github.com/facebook/metro/blob/master/packages/metro/src/node-haste/DependencyGraph.js#L208

  getSha1(filename) {
    // TODO If it looks like we're trying to get the sha1 from a file located
    // within a Zip archive, then we instead compute the sha1 for what looks
    // like the Zip archive itself.
    const splitIndex = filename.indexOf(".zip/");
    const containerName =
      splitIndex !== -1 ? filename.slice(0, splitIndex + 4) : filename; // TODO Calling realpath allows us to get a hash for a given path even when
    // it's a symlink to a file, which prevents Metro from crashing in such a
    // case. However, it doesn't allow Metro to track changes to the target file
    // of the symlink. We should fix this by implementing a symlink map into
    // Metro (or maybe by implementing those "extra transformation sources" we've
    // been talking about for stuff like CSS or WASM).

    const resolvedPath = fs.realpathSync(containerName);

    const sha1 = this._hasteFS.getSha1(resolvedPath);

    if (!sha1) {
      throw new ReferenceError(
        `SHA-1 for file ${filename} (${resolvedPath}) is not computed`
      );
    }

    return sha1;
  }

Here is the function that gets the sha1 from the haste map:

jest-haste-map/build/worker.js

function _getSha() {
  _getSha = _asyncToGenerator(function*(data) {
    const sha1 = data.computeSha1
      ? sha1hex(_gracefulFs().default.readFileSync(data.filePath))
      : null;
    return {
      dependencies: undefined,
      id: undefined,
      module: undefined,
      sha1
    };
  });
  return _getSha.apply(this, arguments);
}

When metro resolves a file it looks for the file in a jest-haste-map which also watches for changes to these files. There are two implementations for building a haste map (crawling the file tree from the project roots):

  1. Facebook’s watchman native (c++, rust, etc.) library. jest-haste-map/build/crawlers/watchman.js
  2. Node.js implementation jest-haste-map/build/crawlers/node.js

Both don’t support following symlinks…so if you have any symlinks in your project root that point to files outside of the project root, then they will never make it into the haste map which also means they will never be watched. It was the first feature request for metro 5 years ago and its unresolved. You can see the issue here: https://github.com/facebook/metro/issues/1#issuecomment-641633646

So there are two situations here.

a. Your symlink points to a file that is inside your project root.

b. Your symlink points to a file outside of your project root. E.g. If you are using a pnpm-based monorepo, all your symlinks resolve to the monorepo root. So if expo is nested inside the monorepo, then you symlinks will always point outside the project root.

For a, your sha-1 will actually be found, because your file’s realpath will be in the haste map. For b, your sha-1 will not be found because your file’s realpath is not in the haste map.

When metro requires a file it needs to transform it. It first checks if its transformation is cached in a metro-cache. The sha-1 of the file is used as part of the cache key.

If its not in the cache, the file contents is actually read not from the haste map but from the file system. So if your file was symlinked to outside of a project root, it won’t be in the haste map at all, and the sha1 can’t be accessed…but it will still be able to be loaded fine.

https://github.com/facebook/metro/blob/7814c2840c49c16788041284fd65df25aa997d8c/packages/metro/src/DeltaBundler/Transformer.js#L141-L147

Re: extraNodeModules

If you look inside metro-resolver#resolve you can see that extraNodeModules[packageName] is added to the list of search paths.

https://github.com/facebook/metro/blob/7814c2840c49c16788041284fd65df25aa997d8c/packages/metro-resolver/src/resolve.js#L108-L134

allDirPaths looks like this:

"/dev/my-monorepo/packages/app-templates/expo/node_modules/@babel/runtime/helpers/interopRequireDefault"
"/dev/my-monorepo/packages/app-templates/node_modules/@babel/runtime/helpers/interopRequireDefault"
"/dev/my-monorepo/packages/node_modules/@babel/runtime/helpers/interopRequireDefault"
"/dev/my-monorepo/node_modules/@babel/runtime/helpers/interopRequireDefault"
"/dev/node_modules/@babel/runtime/helpers/interopRequireDefault"
"/node_modules/@babel/runtime/helpers/interopRequireDefault"
"/dev/my-monorepo/packages/app-templates/expo/node_modules/@babel/runtime/helpers/interopRequireDefault"

Note how the last path comes from the extraPaths.

NOTE: These paths are candidate paths, not dirs to search for packages in.

The extraNodeModules proxy hack is clever, as it allows you to take the packageName and resolve it how you would like. Similar could be done with resolveRequest but its more complicated.

A downside though is you do not get access to where the require is being made from…so it is only really viable if your package is hoisted in yarn. This is unreliable though! If you have two packages of same version, then only one is hoisted and the other is not which could cause crazy bugs.

Overriding resolveRequest is the much safer option.

One spanner in the works though is that inside the resolver context.doesFileExist is called to check if the logical file path exists - not the realpath.

In pnpm:

$ realpath /dev/my-monorepo/packages/expo/node_modules/@babel/runtime/helpers/interopRequireDefault.js

/dev/my-monorepo/node_modules/.pnpm/registry.npmjs.org/@babel/runtime/7.10.2/node_modules/@babel/runtime/helpers/interopRequireDefault.js

So for a monorepo in a pnpm setup, this will prevent files being found, even if you add the monorepo root to the watchFolders.

https://github.com/facebook/metro/blob/7814c2840c49c16788041284fd65df25aa997d8c/packages/metro-resolver/src/resolve.js#L423-L433

  _getFileData(file) {
    const relativePath = fastPath.relative(this._rootDir, file);
    return this._files.get(relativePath);
  }

Including the entirety of the monorepo as a watchFolder may slow down the app startup. Expo will actually work without needing to include every node_module, but then you need to make the sha1 patch and doesFileExist patch.

We can modify ctx.doesFileExist in the resolver#resolveRequest metro config setting. I’m not sure if this will cause problems if resolve a logical path rather than a real path.

LATER: Maybe we could use hasteImplModulePath option to provide a custom haste map that supports symlinks too.


In terms of a clean solution, I think it is best to ensure that all the files that you would possibly require make their way into the haste map, which I think is possible by setting watchFolders in the metro config.


Summary

I’m leaving this long explanation here because I had completely forgotten it since I last looked at it, and was hoping there was a fix by now but it seems not.

  1. Try to include everything in the haste map.

Symlinks will not be followed, so you will need to make sure the folders they point to are added to the haste map by including them as watchFolders.

extraNodeModules doesn’t add files to the haste map, it will only provide additional candidate resolve paths…but they will still need to be in the haste map.

  1. If you can’t put everything in the haste map, then you will need to patch the getSha1 function, and you might need to patch the metro-resolve#resolve function.

I’m thinking there might be a way to monkey-patch the library without needing a patch.

Just need to intercept the require of node_modules/metro/src/node-haste/DependencyGraph.js and override its getSha1 method. Could probably add the require hook when metro.config.js is called…

I had tried all the methods above but still not work for me. OS:mac Catalina node:13.3.0 watchman:4.9.0 react-native:0.61.5

the error log like this

Metro Bundler has encountered an error: SHA-1 for file /Users/.../Downloads/AwesomeProject/node_modules/metro/src/lib/polyfills/require.js (/Users/.../Downloads/AwesomeProject/node_modules/metro/src/lib/polyfills/require.js) is not computed

put all files inside assets name of folder something like this

…/…/assets/beach.jpg

npm cache clean --force cd ios/ && rm -rf build/ && xcodebuild clean npm start – --reset-cache

Worked for me

I fixed it by install correct react-native-cli:

yarn global remove react-native
yarn global add react-native-cli

Works for me!!! 😄

I am experiencing this on RN 0.60.5

I just ran into this issue while enjoying the fun task of upgrading from 0.59.1 to 0.61.5 with react-native-cli.

My error made me realize I was running the bundler via a copy of react-native that was installed to nvm (Node Version Manager). Running which react-native confirmed this: /Users/XXXXXX/.nvm/versions/node/v10.16.2/bin/react-native

I made sure to remove all ‘global’ installs of react-native:

  1. npm -g remove react-native
  2. yarn global remove react-native
  3. brew uninstall react-native-cli

Until which react-native responded with react-native not found

Then in my project, npx react-native run-ios worked.

Mac OS X 10.14.6 RN 0.61.5 npx react-native --version 3.0.4

Seeing the same issue as described on 0.60.5. The workaround provided by @vjpr works - thank you! Looks like #455 should fix this. I’m curious why doesn’t seem to be a bigger problem for everyone 😄

I made the change in ~/.config/yarn/global/node_modules/metro/src/node-haste/DependencyGraph.js

just use npx Ex: npx react-native bundle --dev false --platform android --entry-file index.js --bundle-output ./android/app/src/main/assets/index.android.bundle --assets-dest ./android/app/src/main/res

I found it was an issue with me accessing the project on a second drive via a symlink on C: rather than navigating to the D: drive

Works for me, 0.61.5

For anyone seeing this error when using expo-yarn-workspaces, I got this and fixed it by simply deleting the auto-generated file (the file that the error points to) and deleting all the node modules and then just running yarn again to re-install node modules and re-generate the file.

@donholly I am not sure why no one is flagging this issue more. I have just run in to this issue after I have setup a new terminal and cleaned up all my old node modules.

Thanks to @vjpr workaround it worked after rebuilding the app. Is this a miss on our part ? or is this being actively looked into ?

This is still a problem today with “react-native”: “^0.59.2”. Will there be real fix or do we need to copy/paste the workaround?

  • BUNDLE_FILE=/Users/…/Library/Developer/Xcode/DerivedData/…-aexneivqfarcnddyzdbcbwoienmj/Build/Products/Release-iphoneos/…app/main.jsbundle
  • node /Users/…/git/…-app-react-native/…/node_modules/react-native/cli.js bundle --entry-file index.js --platform ios --dev false --reset-cache --bundle-output /Users/…/Library/Developer/Xcode/DerivedData/…-aexneivqfarcnddyzdbcbwoienmj/Build/Products/Release-iphoneos/…app/main.jsbundle --assets-dest /Users/…/Library/Developer/Xcode/DerivedData/…-aexneivqfarcnddyzdbcbwoienmj/Build/Products/Release-iphoneos/…app warning: the transform cache was reset. Loading dependency graph, done. error SHA-1 for file /Users/…/git/…-app-react-native/…/node_modules/metro/src/lib/polyfills/require.js (/Users/…/git/…-app-react-native/…/node_modules/metro/src/lib/polyfills/require.js) is not computed
  • [[ false != true ]]
  • [[ ! -f /Users/…/Library/Developer/Xcode/DerivedData/…-aexneivqfarcnddyzdbcbwoienmj/Build/Products/Release-iphoneos/…app/main.jsbundle ]]
  • echo ‘error: File /Users/…/Library/Developer/Xcode/DerivedData/…-aexneivqfarcnddyzdbcbwoienmj/Build/Products/Release-iphoneos/…app/main.jsbundle does not exist. This must be a bug with’ error: File /Users/…/Library/Developer/Xcode/DerivedData/…-aexneivqfarcnddyzdbcbwoienmj/Build/Products/Release-iphoneos/…app/main.jsbundle does not exist. This must be a bug with
  • echo ‘React Native, please report it here: https://github.com/facebook/react-native/issues’ React Native, please report it here: https://github.com/facebook/react-native/issues
  • exit 2

If anyone else having issue when archiving ios. I had to install react-native-cli and use it. yarn add react-native-cli then override CLI_PATH image

npm i -g react-native-cli It is solution of this issue.

Just an FYI: was having this issue when running codepush and fixed it by restarting my machine.

If anyone else having issue when archiving ios. I had to install react-native-cli and use it. yarn add react-native-cli then override CLI_PATH image

This one works for me! Thanks!

Try yarn start instead of yarn react-native....

Thanks @vjpr, you saved me hours. I can’t believe Github doesn’t offer some sort of donation button.

@dusan-dragon I needed the @vjpr fix to get RN 0.61 to work.

Just an addition, I ran into this issue today; at first I thought it was due to having pages and routers of my app spread across different libraries using NX, which uses a symlink to node_modules in every app project.

However, the issue was that I already had Metro running in another terminal tab, and kept trying to remove node_modules and restart the app (and Metro) in another tab.

TL;DR make sure there are no other Metro processes running when you try anything described in this thread.

I am getting this issue on rn-tester app on android. None of the above is working for me

I solved this issue by using yarn start instead of starting project with react-native start(it uses npm start). I have been adding dependencies in project using Yarn add {dependencyName} and starting project via npm. which created error of SHA-1.

this did it for me as well. i was running project directly react-native start just did yarn start

I think I found a simple explanation for this problem. See https://github.com/facebook/jest/issues/10063#issuecomment-642356808.

When metro talks to watchman it sends a query with a glob option. When a glob is used it prevents files and folders starting with a dot from being returned by the watchman query. There is an option to pass called glob_includedotfiles which includes dot files.

Depending on whether the clock is available, if glob is not used, then you are not going to get any dotfiles or dot dirs included.

So this explains the sporadic behaviour.

const relativeRoot = fastPath.relative(rootDir, root);
const query = clocks.has(relativeRoot) // Use the `since` generator if we have a clock available
  ? {
      expression,
      fields,
      since: clocks.get(relativeRoot)
    } // Otherwise use the `glob` filter
  : {
      expression,
      fields,
      glob
    };
const response = yield cmd('query', root, query);

This explains why @SeanHayes was having a problem until his MacBook restarted.

And it also explains why pnpm doesn’t work because all its deps are found in node_modules/.pnpm which includes a dir starting with a dot.

Its a one line fix that is in the pipeline but I feel like I went to the ends of the earth to track it down.

Hello, I have this problem and then fixed it by using directly the node_modules/react-native/scripts/launchPackager.command instead of react-native start

If you have any further investigation on this please let me know

Finally got what went wrong here, folder name has 1 upper case character in require. eg. require(../../../assets/marketPlace/banner.png). Here “p” was upper case here but folder was with lower case p. After changing require path it was working again.

0.61.5 indeed works. I also did npm i -g react-native-cli - it updated cli to 2.0.1 version and bundling worked.

currently my project is on 0.61.5 but it showing the same error

I’ve got the same issue when I tried to upgrade react-native to 0.57.0. I followed the steps mentioned the change log (https://github.com/react-native-community/react-native-releases/blob/master/CHANGELOG.md#057), but had no luck.