score:89

Accepted answer

For reusable components:

  1. Put a react dependency in both peerDependencies and devDependencies.
  2. Never put a react dependency in dependencies.

peerDependencies specifies which version(s) of React your reusable component supports/requires. When using npm 2 this also adds React to the list of modules to be installed, but this is no longer the case with npm 3.

devDependencies ensures React will be installed when you run npm install while developing your component, or when running tests on Travis or similar.

Putting react in dependencies will cause multiple versions of React to be installed if somebody uses your component but has a different version of React in their own package.json - having multiple versions of React not only bloats the build, but also causes errors when different versions try to interact.

score:-3

You can have react in either peerDependencies or in dependencies. The difference being that with a peerDependencies, react is only installed once for the package using your package. If you put it in dependencies, react will be installed twice, one time for the package using your package, and once time for your package.

React itself seems to favor peerDependencies for some reason. You obviously don't want two separate versions of react in your Javascript bundle (which happens by default if you use dependencies), but that is easy to fix with npm dedupe.

So there's no correct way to do it, both peerDependencies and dependencies work. Using dependencies is more in line with the node/NPM way, but using peerDependencies is friendlier to users of your package that doesn't know about npm dedupe and why it's needed.

score:9

The selected answer is definitely the prescribed approach here however I've started favoring the use of inversion of control as opposed to relying on npm peer dependencies for my libraries dependencies and its served me well.

Libraries are easier if you build them functional. It seems to be easier to maintain libraries that export a single function which takes in an object with all of their heavy dependencies and export an object containing each of your libraries typical exports.


Library 'injected'

lib/index.js

export default ({ React }) => {
  const InjectedComponent = props => (
    <p style={{color: props.color}}>This component has no React npm dependencies.</p>
  )

  /** other stuff */

  return { InjectedComponent }
}

Consuming App

app.js

import React from 'react'
import { render } from 'react-dom'

/** Import the default export factory from our library */
import createInjectedComponent from 'injected'

/** Call the factory, passing its dependencies (guaranteed to match what we're bundling) and get back our component */
const { InjectedComponent } = createInjectedComponent({ React })

render(<InjectedComponent color="blue" />, document.getElementById('root'))

If your component only works with a given version of react or some other dependency, you can write some assertions around the version for the React parameter that is passed in. Overall, building libraries in this fashion should be less prone to new build issues appearing anytime version of React is published and will more importantly ensure that you are not causing your library consumers to bundle multiple versions of React and other heavy libraries. This pattern works well with npm link (I generally have 16+ libraries running from npm link simultaneous and experienced issues when I didn't use this pattern).

In your main app I would recommend always splitting out react, react dom and any react lib components you use into a vendor bundle (webpack) and mark it as external in your main bundle so that you do not unintentionally bundle two versions.


Related Query

More Query from same tag