npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@rnx-kit/metro-config

v1.3.15

Published

Metro config for monorepos

Downloads

129,946

Readme

@rnx-kit/metro-config

Build npm version

@rnx-kit/metro-config provides helper functions for creating a Metro config that works in a monorepo.

Installation

yarn add @rnx-kit/metro-config --dev

or if you're using npm

npm add --save-dev @rnx-kit/metro-config

Usage

Import makeMetroConfig helper function from @rnx-kit/metro-config:

// metro.config.js
const { makeMetroConfig } = require("@rnx-kit/metro-config");

module.exports = makeMetroConfig({
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: false,
      },
    }),
  },
});

makeMetroConfig takes a Metro config as parameter. The config is augmented with additional fields to make sure it works smoothly in a monorepo.

Known Limitations

While Metro is the de-facto standard, it lacks a few features that would make it optimal; here's our current wishlist, feel free to submit PRs if you want to help with them :)

Bundle size

Metro currently does not implement tree shaking, i.e. it does not attempt to remove unused code from the JS bundle. For instance, given this code snippet:

import { partition } from "lodash";

Metro will bundle all of lodash in your bundle even though you're only using a small part of it. In lodash's case, you can add babel-plugin-lodash to your Babel config to help Metro strip away some modules, but not all libraries will come with such helpers.

If you're feeling adventurous, you can try an experimental Metro serializer we've built that adds support for tree shaking: @rnx-kit/metro-serializer-esbuild. Do note that you will need to be on React Native 0.64 or above, and use Metro 0.66.1.

Plugins

Metro doesn't have a plugin system, but it does have hooks that allows you to implement something that functions similarly. @rnx-kit/metro-serializer implements a serializer that allows you to pass plugins that are run just before the JS bundle is serialized and written to disk. Follow the instructions for installing it, then try our plugins:

You can of course also provide your own plugins.

Ensuring a single instance of a package

Normally, Metro resolves a module relative to the package it is currently residing in. For example, with a monorepo such as below, my-awesome-package would resolve [email protected] while another-awesome-package would resolve [email protected]. This would lead to duplicate packages in your bundle and may cause issues.

workspace
├── node_modules
│   └── [email protected]  <-- should be ignored
└── packages
    ├── my-awesome-package
    │   └── node_modules
    │       └── [email protected]  <-- should take precedence
    └── another-awesome-package  <-- imported by my-awesome-package,
                                     but uses workspace's react-native-msal

If we simply exclude the workspace's copy, Metro will not be able to find react-native-msal from another-awesome-package. It also won't exclude copies that are installed in other packages. To help Metro resolve to the correct copy, we need to exclude all other copies, and also add a corresponding entry in extraNodeModules. @rnx-kit/metro-config contains functions to help you set this up correctly. Given the example above, our metro.config.js should look like this:

const {
  exclusionList,
  makeMetroConfig,
  resolveUniqueModule,
} = require("@rnx-kit/metro-config");

const [msalPath, msalExcludePattern] = resolveUniqueModule("react-native-msal");
const additionalExclusions = [msalExcludePattern];
const blockList = exclusionList(additionalExclusions);

module.exports = makeMetroConfig({
  resolver: {
    extraNodeModules: {
      "react-native-msal": msalPath,
    },
    blacklistRE: blockList, // For Metro < 0.60
    blockList, // For Metro >= 0.60
  },
});

Error: EMFILE: too many open files, watch

If you're getting an error like below, you need to install Watchman.

events.js:292
      throw er; // Unhandled 'error' event
      ^

Error: EMFILE: too many open files, watch
    at FSEvent.FSWatcher._handle.onchange (internal/fs/watchers.js:178:28)
Emitted 'error' event on NodeWatcher instance at:
    at NodeWatcher.checkedEmitError (/~/node_modules/sane/src/node_watcher.js:143:12)
    at FSWatcher.emit (events.js:315:20)
    at FSEvent.FSWatcher._handle.onchange (internal/fs/watchers.js:184:12) {
  errno: -24,
  syscall: 'watch',
  code: 'EMFILE',
  filename: null
}

Error: jest-haste-map: Haste module naming collision

Metro will throw an exception if it finds duplicates:

Error: jest-haste-map: Haste module naming collision:
  Duplicate module name: react-animated
  Paths: /~/node_modules/example/node_modules/react-native/Libraries/Animated/release/package.json collides with /~/node_modules/react-native/Libraries/Animated/release/package.json

This error is caused by `hasteImpl` returning the same name for different files.
    at setModule (/~/node_modules/jest-haste-map/build/index.js:569:17)
    at workerReply (/~/node_modules/jest-haste-map/build/index.js:641:9)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    at async Promise.all (index 77)

To resolve this, you'll need to exclude one of the paths in your metro.config.js, e.g.:

const { exclusionList, makeMetroConfig } = require("@rnx-kit/metro-config");

const blockList = exclusionList([
  // Ignore nested copies of react-native
  /node_modules\/.*\/node_modules\/react-native\/.*/,
]);

module.exports = makeMetroConfig({
  resolver: {
    blacklistRE: blockList, // For Metro < 0.60
    blockList, // For Metro >= 0.60
  },
});

[Flipper] React DevTools is disabled

Flipper only enables React Native plugins if it detects a Metro "device". It detects one by opening localhost:8081 and checking for some keywords like "React Native packager is running". However, if one of your packages have an index.html in the package root, Metro will serve that file instead. Flipper will then think that it's not dealing with a React Native app and disable all related plugins.

The fix is to move index.html elsewhere, but if you cannot do that, you can work around this issue by filtering out the offending packages in metro.config.js:

const { makeMetroConfig } = require("@rnx-kit/metro-config");
const fs = require("fs");
const path = require("path");

const config = makeMetroConfig();

module.exports = {
  ...config,
  watchFolders: config.watchFolders.filter(
    (p) => !fs.existsSync(path.join(p, "index.html"))
  ),
};