flisboac/react-native-monorepo-helper

Question about use of the module

stephane-r opened this issue · 2 comments

  • I'm submitting a ...
    [ ] bug report
    [ ] feature request
    [ ] question about the decisions made in the repository
    [x ] question about how to use this project

  • Summary

Hi :)

I try to init React monorepo projet with React-Native, React web, and common modules. I would like try your nice module, but how use it after create the project structure ?

Thank you !

Hello, @stephane-r ! I'm really sorry for the late reply, it seems I had misconfigured github's notifications and I didn't see your question in time!

First, some remarks

This project is only meant to be a configuration builder that helps you set up the correct set of values to make React Native understand where your hoisted/symlinked dependencies are, if any (and this may include your monorepo modules).

react-native-monorepo-helper is not a plugin of any sort; you merely require('react-native-monorepo-helper') and "build" your rn-cli.config.js's configuration according to your project's needs.

react-native-monorepo-helper acts only in your React Native's project, and it only exists because:

  1. Metro doesn't follow symlinks (which are created when hoisting dependencies); and
  2. React Native seems to not be able to properly resolve hoisted dependencies that follow the new module resolution rules, for some reason (at least in my experience; that "Haste module map" is probably the most esoteric black box I've seen in ages).

Yarn has a wondeful explanation of what "hoisting" actually is: https://yarnpkg.com/blog/2018/02/15/nohoist/

Project layout

The helper assumes you either use:

  1. Yarn Workspaces; or
  2. Lerna.

It'll try to check for Lerna first. Supposing your project is properly configured and doesn't duplicate information, it'll correctly and exactly identify your monorepo's package/project folders by reading your lerna.json (for Lerna) or package.json (for Yarn Workspaces).

Converting your rn-cli.config.js

If you already have a monorepo project laid out, you only need to substitute whatever you have exported in rn-cli.config.js by whatever the helper builds in the end. For example, suppose you currently have this in your rn-cli.config.js:

const path = require('path');
module.exports = {
  projectRoot: __dirname,
  watchFolders: [
    path.resolve(__dirname),
    path.resolve(__dirname, '../non-project-module'),
  ],
  port: 8090
};

A naive (or immediate) conversion would require you to:

  1. Extract projectRoot from the config (whatever value it is; most probably, __dirname);
  2. Create the helper by passing to it the projectRoot;
  3. Provide additional configuration, if needed;
  4. Provide extra Metro/Haste configuration via helper.defaultOptions(); and
  5. Export the result of helper.generate().

A fully converted and correct (and quite verbose) rn-cli.config.js using the config helper:

// Here are your old imports and configuration
const path = require('path');
const oldConfig = {
  projectRoot: __dirname,
  watchFolders: [
    path.resolve(__dirname),
    path.resolve(__dirname, '../non-project-module'),
  ],
  port: 8090
};

// Import the config helper builder
const { metroConfigHelper } = require('react-native-monorepo-helper');

// Create the config helper (which is actually a builder)...
const helper = 
  // ... with your project's root folder
  metroConfigHelper(oldConfig.projectRoot)
  // ... and give to the builder your old configuration; the builder will override 
  // your default configuration whenever needed, otherwise its values are
  // passed along as-is when `generate()` is called
  .defaultConfig(oldConfig)
  // ... optionally, the helper can properly configure your rn-cli.config.js
  // to work with e.g. typeScript. See the documentation for more details.
  // (But I confess that the documentation is lacking! Sorry! :< )
  .typeScript(true);

// Create the new config
const newConfig = helper.generate();

// Export the new config
module.exports = newConfig;

Internally, react-native-monorepo-helper will re-set projectRoot, watchFolders and some fields in resolver with exactly the values that will allow for proper hoisted/symlinked dependency resolution in Haste (please see here for more details on how the final configuration object is built).

If your rn-cli.config.js only has watchFolders that refer to monorepo modules, you can omit it altogether. Otherwise, prefer to use helper.watchFolder() instead.

Now, a simplified version of the previous example:

const path = require('path');
const { metroConfigHelper } = require('react-native-monorepo-helper');

module.exports =  metroConfigHelper(projectRoot)
  .defaultConfig({
    port: 8090
  })
  .watchedFolder(path.resolve(__dirname, '../non-project-module')),
  .typeScript(true)
  .generate();

For more details on what fields are valid in defaultConfig(), please refer to Metro's documentation.

About nohoist and final remarks

Unfortunately some dependencies cannot be hoisted. For those dependencies, you need to tell your monorepo manager to avoid hoisting them, i.e. nohoisting.

By my knowledge, you need to nohoist:

  • Any official react-native dependencies ( e.g. the react-native package itself); and
  • Any React transformers and/or plugins (e.g. react-native-typescript-transformer).

You may also need to nohoist packages that are meant to be react-native linked, but that's not necessarily a rule. If Metro/Haste is complaining about a missing dependency in the Haste module map, try nohoisting it -- until we can figure out a better solution.

The way you nohoist dependencies may vary depending on which package/monorepo manager you use.

  • For Yarn, see here;
  • For Lerna, see here.

Sorry if i dragged this for too long. Also, please do not hesitate to ask if you have more questions! :)

Closing the issue. If you still have any questions, or need to clear anything up, please do not hesitate to comment!