将 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
我有这个带有 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