Skip to content

Instantly share code, notes, and snippets.

@mbret
Last active October 1, 2019 16:06
Show Gist options
  • Save mbret/edbfec5df3adac6dbf980b43be08f556 to your computer and use it in GitHub Desktop.
Save mbret/edbfec5df3adac6dbf980b43be08f556 to your computer and use it in GitHub Desktop.
Symlink for React Native 0.59 (symlink + scoped package + hast name collision)
/**
* Metro configuration for React Native
* https://github.com/facebook/react-native
*
* @format
*/
const {
getSymLinkedModules,
getBlacklistedModulesForAlternateRoot,
getExtraModulesForAlternateRoot,
escapeRegExp
} = require("./metroConfigUtils");
const fs = require("fs");
// Get blacklist factory
const blacklist = require("metro-config/src/defaults/blacklist");
let moduleBlacklist = [];
// alternate roots (outside of project root)
const alternateRoots = getSymLinkedModules();
// resolve external package dependencies by forcing them to look into path.join(__dirname, "node_modules")
const extraNodeModules = alternateRoots.reduce((obj, item) => {
Object.assign(obj, getExtraModulesForAlternateRoot(item));
return obj;
}, {});
alternateRoots.forEach(root => {
const modules = getBlacklistedModulesForAlternateRoot(root);
moduleBlacklist = moduleBlacklist.concat(
Object.keys(modules).map(key => RegExp(`${escapeRegExp(`${modules[key]}\\`)}.*`))
);
});
console.log(alternateRoots)
module.exports = {
watchFolders: alternateRoots,
resolver: {
extraNodeModules,
blacklistRE: blacklist(moduleBlacklist)
},
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
},
};
const path = require("path");
const fs = require("fs");
const baseModulePath = path.resolve(__dirname, "node_modules");
exports.getSymLinkedModules = function () {
function checkModule(fileName, alternateRoots, modulePath) {
try {
const fullFileName = path.join(modulePath, fileName);
const stats = fs.lstatSync(fullFileName);
if (stats.isSymbolicLink()) {
const realPath = fs.realpathSync(fullFileName);
if (realPath.substring(0, (__dirname).length) !== __dirname) alternateRoots.push(realPath);
}
} catch (e) {
// void
}
}
function checkAllModules(modulePath, alternateRoots) {
const stats = fs.lstatSync(modulePath);
if (!stats.isDirectory()) return alternateRoots;
fs.readdirSync(modulePath).forEach((fileName) => {
if (fileName.charAt(0) === ".") return;
if (fileName.charAt(0) !== "@") checkModule(fileName, alternateRoots, modulePath);
else checkAllModules(path.join(modulePath, fileName), alternateRoots);
});
return alternateRoots;
}
return checkAllModules(baseModulePath, []);
}
exports.getExtraModulesForAlternateRoot = function (fullPath) {
const packagePath = path.join(fullPath, "package.json");
const packageJSON = require(packagePath);
const alternateModules = [].concat(
Object.keys(packageJSON.dependencies || {}),
Object.keys(packageJSON.devDependencies || {}),
Object.keys(packageJSON.peerDependencies || {})
);
const extraModules = {};
for (let i = 0, il = alternateModules.length; i < il; i++) {
const moduleName = alternateModules[i];
extraModules[moduleName] = path.join(baseModulePath, moduleName);
}
return extraModules;
}
exports.getBlacklistedModulesForAlternateRoot = function (fullPath) {
const packagePath = path.join(fullPath, "package.json");
const packageJSON = require(packagePath);
const alternateModules = [].concat(
Object.keys(packageJSON.peerDependencies || {})
);
const extraModules = {};
for (let i = 0, il = alternateModules.length; i < il; i++) {
const moduleName = alternateModules[i];
extraModules[moduleName] = path.join(fullPath, "node_modules", moduleName);
}
return extraModules;
}
exports.escapeRegExp = function (string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
@jacargentina
Copy link

jacargentina commented Apr 15, 2019

@mbret

Hey i'm trying your gist on my project, it seems i'm not using it the right way.

i have this:
~/git/react-native-myModule/
~/git/myApp/

I've tried:

~/git/react-native-myModule/
npm install
npm link
~/git/myApp/
npm link react-native-myModule

Then applied your metro.config on ~/git/myApp/ folder

Now metro bundler auto detects the symlinked react-native-myModule just fine, but i'm getting naming collisions like this

Loading dependency graph...info Launching Dev Tools...
(node:3702) UnhandledPromiseRejectionWarning: Error: jest-haste-map: Haste module naming collision:
  Duplicate module name: Sample
  Paths: ~/git/react-native-myModule/node_modules/react-native/Libraries/Sample/package.json collides with ~/git/myApp/node_modules/react-native/Libraries/Sample/package.json

This error is caused by `hasteImpl` returning the same name for different files.

I'm missing something?

@zeevl
Copy link

zeevl commented May 20, 2019

This made my day. THANK YOU!!!

@wkoutre
Copy link

wkoutre commented Aug 6, 2019

🔥 !! Thanks @mbret

@xxRockOnxx
Copy link

:\ I get

Module `@babel/runtime/regenerator` does not exist in the Haste module map or in these directories:

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