skip to Main Content

Apology for the winding question title which might not make sense at first sight. Let me explain.

I have a library package named foo-bar-ui for some shared UI components. All the components are exported from a single index file.

// index.js
export * from './lib/ComponentA';
export * from './lib/ComponentB';
export * from './lib/ComponentC';

And in the application package, I can happily import things from this library in below format.

import { ComponentA, ComponentB } from 'foo-bar-ui';

And one day I added a new third party dependency react-xyz for ComponentC. It’s only used by ComponentC, hence I declared it as a peer dependency in the library package. Any client application consuming this library can install react-xyz on its own if it needs to use ComponentC.

However, my current application only consumes ComponentA and ComponentB. Webpack bundling has no complaint at all as it does tree-shaking, which discards ComponentC completely hence there is no warning about missing react-xyz. But Jest complains on that. Below execution failure is observed in client applications consuming the library package, who did not install react-xyz as they do not use ComponentC.

Test suite failed to run

    Cannot find module 'react-xyz' from 'node_modules/foo-bar-ui/lib/ComponentC.js'

    Require stack:
      node_modules/foo-bar-ui/lib/ComponentC.js
      node_modules/foo-bar-ui/index.js'

The immediate solution seems to be exporting each individual components separately, like what lodash-es does, e.g. import isNil from 'lodash-es/isNil';. And Node.js subpath exports seems to be the tool doing the job. But I found it problematic with TypeScript, ESLint and Jest all together with module resolution issues.

What’s the ultimate recommended approach to solve this problem? It seems straightforward but oddly I could not land on any easy and succinct solution right away.

2

Answers


  1. Chosen as BEST ANSWER

    For the current being, a temporary solution I opted in is to stop using the single index and directly take the exports from the individual components.

    So in a client application, instead of

    import { ComponentA, ComponentB } from 'foo-bar-ui';
    

    I would have

    import { ComponentA } from 'foo-bar-ui/dist/features/componentA';
    import { ComponentB } from 'foo-bar-ui/dist/features/componentB';
    

    This works but it's just that the import statements are not as neat as before.

    We could try to eliminate the /dist/features portion. Subpath exports could be of help, but I found it to be not working well with TypeScript, ESLint and Jest etc.


  2. It’s best if you cover both of the scenarios, when the library is installed and when it’s not. And jest.mock supports "virtual" modules, allowing you to mock non-existent modules, so your test can look like this:

    describe('ComponentC', () => {
      describe('with react-xyz', () => {
        beforeAll(() => {
          jest.mock('react-xyz', () => {
            // return some implementation
          }, { virtual: true });
        })
      });
    
      describe('without react-xyz', () => {
        beforeAll(() => {
          jest.mock('react-xyz', () => {
            // throw new Error('Cannot find module')
          }, { virtual: true });
        })
      });
    })
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search