将 noHoist 添加到项目后,React Native 无法解析本地模块

React native couldn't resolve local module after noHoist has been added to project

我有这个带有 yarn 工作区和 lerna 的 monorepo js 设置

/package.json
/packages
   /common (js shared code)
     /package.json 
   /mobile (react native - metro)
     /package.json
   /web (CRA)
     /package.json

移动和网络包正在导入公共包 package.json 如下

"dependencies": {
    "common": "*",
} 

我必须在根 package.json 中添加 noHoist 选项,这样移动原生依赖项就不会被提升,所以构建脚本仍然 运行 没问题

"workspaces": {
    "packages": [
      "packages/*"
    ],
    "nohoist": [
      "**/react-native",
      "**/react-native/**"
    ]
  }

Web 在添加 noHoist 选项之前和之后都运行良好

添加 noHoist 后,React 本机 Metro 捆绑开始失败......它显示

"Error: Unable to resolve module .. could not be found within the project or in these directories:
  node_modules
  ../../node_modules" 

然而 common 包确实存在于 root node_modules 下? 看起来像某种 linking 问题! (确实尝试 link 手动/同样的问题)..注意我没有在 noHoist

下添加通用包

这是我的地铁配置的样子

const path= require('path');

const watchFolders = [   
  path.resolve(`${__dirname}`), // Relative path to package node_modules   
  path.resolve(`${__dirname}/../../node_modules`), // Relative path to root node_modules ];

module.exports = {   
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: true,
      },
    }),},
  maxWorkers: 2,
  watchFolders, };

有什么想法吗?

事实证明问题出在捆绑中,通过编辑 metro.config.js 以包含阻止列表和 extraNodeModules

来修复
const path = require('path');
const exclusionList = require('metro-config/src/defaults/exclusionList');
const getWorkspaces = require('get-yarn-workspaces');

function generateAssetsPath(depth, subpath) {
  return `/assets`.concat(
    Array.from({ length: depth })
      // eslint-disable-next-line no-unused-vars
      .map((_, i) => `/${subpath}`)
      .join(''),
  );
}

function getMetroAndroidAssetsResolutionFix(params = {}) {
  const { depth = 3 } = params;
  let publicPath = generateAssetsPath(depth, 'dir');
  const applyMiddleware = (middleware) => (req, res, next) => {
    // eslint-disable-next-line no-plusplus
    for (let currentDepth = depth; currentDepth >= 0; currentDepth--) {
      const pathToReplace = generateAssetsPath(currentDepth, 'dir');
      const replacementPath = generateAssetsPath(depth - currentDepth, '..');
      if (currentDepth === depth) {
        publicPath = pathToReplace;
      }
      if (req.url.startsWith(pathToReplace)) {
        req.url = req.url.replace(pathToReplace, replacementPath);
        break;
      }
    }
    return middleware(req, res, next);
  };
  return {
    publicPath,
    applyMiddleware,
  };
}

function getNohoistedPackages() {
  // eslint-disable-next-line global-require
  const monorepoRootPackageJson = require('../../package.json');
  const nohoistedPackages = monorepoRootPackageJson.workspaces.nohoist
    .filter((packageNameGlob) => !packageNameGlob.endsWith('**'))
    .map((packageNameGlob) => packageNameGlob.substring(3));
  return nohoistedPackages;
}

function getMetroNohoistSettings({
  dir,
  workspaceName,
  reactNativeAlias,
} = {}) {
  const nohoistedPackages = getNohoistedPackages();
  const blockList = [];
  const extraNodeModules = {};
  nohoistedPackages.forEach((packageName) => {
    extraNodeModules[packageName] =
      reactNativeAlias && packageName === 'react-native'
        ? path.resolve(dir, `./node_modules/${reactNativeAlias}`)
        : path.resolve(dir, `./node_modules/${packageName}`);
    const regexSafePackageName = packageName.replace('/', '\/');
    blockList.push(
      new RegExp(
        `^((?!${workspaceName}).)*\/node_modules\/${regexSafePackageName}\/.*$`,
      ),
    );
  });
  return { extraNodeModules, blockList };
}

const workspaces = getWorkspaces(__dirname);

const androidAssetsResolutionFix = getMetroAndroidAssetsResolutionFix({
  depth: 3,
});

const nohoistSettings = getMetroNohoistSettings({
  dir: __dirname,
  workspaceName: 'mobile',
});

module.exports = {
  transformer: {
    // Apply the Android assets resolution fix to the public path...
    // publicPath: androidAssetsResolutionFix.publicPath,
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: true,
      },
    }),
  },
  // server: {
  //   // ...and to the server middleware.
  //   enhanceMiddleware: (middleware) =>
  //     androidAssetsResolutionFix.applyMiddleware(middleware),
  // },
  // Add additional Yarn workspace package roots to the module map.
  // This allows importing importing from all the project's packages.
  watchFolders: [
    path.resolve(__dirname, '../../node_modules'),
    ...workspaces.filter((workspaceDir) => !(workspaceDir === __dirname)),
  ],
  maxWorkers: 2,
  resolver: {
    // Ensure we resolve nohoisted packages from this directory.
    blockList: exclusionList(nohoistSettings.blockList),
    extraNodeModules: nohoistSettings.extraNodeModules,
  },
};

你可以查看this universal CRA/RN mono-repo that uses such metro configs