19

I have a library of multiple react components, and I want to make the library tree-shakable so that when I import a component like

import { Checkbox } from 'my-react-components'

I don't import the whole bundle.

My index.js looks like this

export { default as Button } from './components/Button'
export { default as Checkbox } from './components/Checkbox'
export { default as FlexView } from './components/FlexView'
export { default as Radio } from './components/Radio'
export { default as Select } from './components/Select'
export { default as TextInput } from './components/TextInput'
export { default as Toggle } from './components/Toggle'

And I bundle using webpack

module.exports = {
  mode: 'production',
  entry: './src/index.ts',
  output: {
    path: path.resolve('./lib'),
    filename: 'react-components.js',
    libraryTarget: 'commonjs2',
  },

  // loaders and stuff...
  // terser-webpack-plugin...

  externals: {
    // don't include react in the bundle
    react: {
      root: 'React',
      commonjs2: 'react',
      commonjs: 'react',
      amd: 'react',
    },
  },
  optimization: {
    splitChunks: false,
    sideEffects: false,
  },
}

And in my babel config of course I have

['@babel/preset-env', {
  modules: false,
}]

With this setup when I import only one component, and the whole bundle gets included (I am using webpack also when I import it). How do I prevent this?

Pontiacks
  • 1,118
  • 2
  • 13
  • 23

4 Answers4

3

As of February 2020 it is not possible with Webpack.

Because tree-shaking mechanism heavily relies on ES6 import/export and Webpack currently doesn't support ES2015 module format in output.libraryTarget.

As an alternative you can bundle your library with other bundlers like Rollup (which does support ES6 modules for libraries).

You can keep track of this feature here: https://github.com/webpack/webpack/issues/2933

UPDATE: Though there is hacky workaround: https://stackoverflow.com/a/60010871/921193

WelcomeTo
  • 19,843
  • 53
  • 170
  • 286
2

I was able to get this working by directly importing the file from the library's lib folder, instead of just naming the library by itself.

in my case, I imported some of the components from Reactstrap library like this:

import Button from 'reactstrap/lib/Button';
import UncontrolledPopover from 'reactstrap/lib/UncontrolledPopover';
import PopoverHeader from 'reactstrap/lib/PopoverHeader';
import PopoverBody from 'reactstrap/lib/PopoverBody';

1

I believe you also need the module property on your package.json

{
   ...
   "main": "lib/index.js",
   "module": "lib/index.esm.js",
   ...
}
Alejandro Garcia Anglada
  • 2,373
  • 1
  • 25
  • 41
0

There is an effective babel plugin to help transform your import according to the rules you specified.

[
   'transform-imports', // or 'babel-plugin-transform-imports'
   {
     'react-router': {
       transform: 'react-router-dom/${member}',
       preventFullImport: true
     },
     reactstrap: {
       transform: 'reactstrap/lib/${member}',
       preventFullImport: true
     }
   }
]

Note: preventFullImport is useful if you want to make sure your import to that path is tree-shakeable.


This is just another way of implementation to help you effectively tree-shake unused import. But importing directly per file would be the best practice in the long run.

import Button from 'reactstrap/lib/Button';
import UncontrolledPopover from 'reactstrap/lib/UncontrolledPopover';
Joshua Stephen
  • 196
  • 1
  • 8