Metro Bundler Symlink Support
metro-symlinked-deps is a utility package designed to configure the Metro bundler, primarily used in React Native development, to overcome its inherent lack of support for symlinks. This deficiency often hinders local development workflows involving `yarn link` or `npm link` for shared dependencies by preventing Metro from resolving modules correctly across symlinked directories. Version 2.0.0 is the current stable release, which maintains compatibility with various `metro-config` versions by designating it as a peer dependency, thereby avoiding duplicate installations. The package provides a streamlined, automatic configuration function, `applyConfigForLinkedDependencies`, which intelligently manages Metro's `resolver.blacklistRE` and `watchFolders` settings. It addresses a long-standing issue in Metro (and its reliance on `jest-haste-map`) by allowing developers to integrate symlinked packages into their build process seamlessly. While not on a strict release cadence, it is updated as needed to address dependency changes and maintain its workaround effectiveness as long as Metro's native symlink issue persists.
Common errors
-
Unable to resolve module `your-linked-module` from `path/to/project`
cause Metro's resolver is not correctly configured to find the symlinked module, indicating the workaround might not be fully effective or the cache is stale.fixVerify that `metro-symlinked-deps` is correctly applied in your `metro.config.js`. Ensure `projectRoot` and `additionalWatchFolders` are accurately configured to include your linked packages. Always run `npx react-native start --reset-cache` after making changes to your configuration or linking new dependencies. -
Error: Invariant Violation: ModuleAppRegistry is not a registered callable module (calling runApplication)
cause This error frequently arises from bundling multiple instances of `react-native`, typically when a linked dependency also includes `react-native` in its `node_modules`.fixAdd `react-native` to the `blacklistLinkedModules` option in your `metro-symlinked-deps` configuration within `metro.config.js`: `{ blacklistLinkedModules: ['react-native'] }`. Subsequently, clear the Metro cache using `npx react-native start --reset-cache`. -
ReferenceError: require is not defined (or similar errors with import/export in metro.config.js)
cause The `metro.config.js` file is typically executed in a CommonJS environment by Node.js. Using ES module `import`/`export` syntax directly without proper setup will lead to errors.fixEnsure your `metro.config.js` exclusively uses CommonJS `require()` and `module.exports` syntax, as demonstrated in the package's examples. If you intend to use ESM, your project's Node.js environment and Metro's configuration would need specific, advanced setup (e.g., setting `"type": "module"` in `package.json` and ensuring compatibility).
Warnings
- gotcha Metro bundler fundamentally lacks native symlink support, which is the underlying problem this package addresses. Misconfigurations or incomplete application of this workaround can lead to issues that mimic core Metro problems like 'Module not found' or incorrect module resolution.
- gotcha Failure to explicitly provide the `projectRoot` option in `applyConfigForLinkedDependencies` can lead to incorrect detection of the project's root, causing linked dependencies or watch folders to be missed or resolved incorrectly.
- gotcha Naming collisions can occur, especially with common packages like `react-native` if they are included as a `devDependency` in a linked package. This often results in runtime errors such as 'Invariant Violation: ModuleAppRegistry is not a registered callable module'.
- breaking Since version 2.0.0, `metro-config` transitioned from a direct dependency to a peer dependency. This change requires `metro-config` to be explicitly installed as a top-level dependency in your project's `package.json` for `metro-symlinked-deps` to function correctly.
Install
-
npm install metro-symlinked-deps -
yarn add metro-symlinked-deps -
pnpm add metro-symlinked-deps
Imports
- applyConfigForLinkedDependencies
import applyConfigForLinkedDependencies from 'metro-symlinked-deps';
import { applyConfigForLinkedDependencies } from 'metro-symlinked-deps'; - applyConfigForLinkedDependencies
const applyConfigForLinkedDependencies = require('metro-symlinked-deps');const { applyConfigForLinkedDependencies } = require('metro-symlinked-deps'); - Default export (non-existent)
import MetroSymlinkedDeps from 'metro-symlinked-deps';
/* No default export is provided by this package. */
Quickstart
const { applyConfigForLinkedDependencies } = require('metro-symlinked-deps');
const { getDefaultConfig } = require('metro-config');
module.exports = (async () => {
const defaultConfig = await getDefaultConfig();
return applyConfigForLinkedDependencies(
{
...defaultConfig,
// Add any custom Metro configuration here that you need to merge
transformer: {
babelTransformerPath: require.resolve('react-native-babel-transformer'),
},
resolver: {
...defaultConfig.resolver,
extraNodeModules: {
// Example for a specific linked module if needed
'some-linked-module': __dirname + '/../some-linked-module',
},
},
},
{
projectRoot: __dirname, // Explicitly provide the project root
blacklistLinkedModules: ['react-native'], // Blacklist common collision source
additionalWatchFolders: [
// Add paths to any top-level linked packages here if not automatically detected
__dirname + '/../path-to-your-linked-package-root'
]
},
);
})();