Search code examples
webpackbabeljspeer-dependencies

How to output NPM packages with peer dependencies that allow for selective imports?


I am publishing a module that uses a few peer dependencies:

In my package.json file i declare peerDependencies:

...
"peerDependencies": {
  "react": ">=16",
  "react-dom": ">=16",
  "@material-ui/core": ">=4",
  "@material-ui/icons": ">=4"
},
...

And in my webpack config I declare externals along with an output format of commonjs2 (I'm not at all sure why I would use commonjs2 instead of commonjs):

...
output: {
  filename: 'index.js',
  libraryTarget: 'commonjs2',
  path: path.join(__dirname, output),
},
externals: {
  react: 'react',
  'react-dom': 'react-dom',
  '@material-ui/core': '@material-ui/core',
  '@material-ui/icons': '@material-ui/icons',
},
...

That seems to be working in that I can see the externals are NOT included in the output file. However, including the output of this package in my project results in a MUCH larger bundle size.

I think it's because the output of the bundle converts my import statements to require statements, so that in my project where I'm using this package the peer dependencies defined as require statements can't be optimized.

In other words, the output of the package, instead of including the @material-ui/core library as part of the build, will instead reference it as require('@material-ui/core')

Is that correct?

What is the correct way to package peer dependencies so that:

  1. They adhere to the commonjs module format (is this necessary)?
  2. Indirect imports of the peer dependencies can still be selective?

I THINK (but could be wrong) that what is happening at the moment is that:

// In my project where I consume the package
import { Thing } from '@org/package-name'

// And in @org/package-name` source code, I have
import { Card } from '@material-ui/core'

// Which gets transpiled to (more or less)
require('@material-ui/core').Card

// Which means that when I use "Thing", that I'm loading the whole of @material-ui/core

I've also tried configuring babel with babel-plugin-transform-imports. But I'm finding that I would then have to configure the peer dependencies / externals for every import (@material-ui/core/Card, @material-ui/core/List, etc. etc.)


Solution

  • The working solution, I have found, is to set targets.esmodules to true in the babel configuration for preset-env.

    I think that the reason this worked for me is that I was re-exporting modules in a single entry. And the named exports in the commonjs format can't easily be isolated on import?

    But that's just a guess.