简体   繁体   中英

Barrel files, webpack and jest

I have few questions about barrel files, webpack and jest. I've never really wondered how they work and now I'm struggling (with jest) to write tests on a bigger application that does not have ones yet.

I have barrel files in big folders (like components ) and they look like this:

/components/index.js

export { default as ComponentA } from './ComponentA';
export { default as ComponentB } from './ComponentB';
export { default as ComponentC } from './ComponentC';

With a setup like this I can easily import those components like this:

import { ComponentA, Component C } from '/components';

instead of writting

import Component A from '/components/ComponentA';
import Component C from '/components/ComponentC';

My main question: In my webpack bundled files, will the ComponentB file be included just because I have it in the components/index.js (but I do not really use it)?

This came to my mind after I started writting tests with jest and it started to throw me errors about files I haven't writted tests yet for. I've tried to search why, and the only reason I can find is that I have imports from barrel files in bigger files (eg I import ComponentA from a barrel file when building a page - that I'm now trying to test).

Actually yes, all files imported in a barrel file are currently included in the import on Jest. That happens mainly because Jest uses CommonJS and does not do any tree-shaking (which would actually just make everything even slower in this use case).

When working on a big project, it seems to be quite common to have Jest test suites run increasingly slow when using barrel files and that is related to the fact that the entire dependency tree has to be resolved before running the tests.

The quick solution for that is to not use barrel files for imports within the package, either by avoiding them completely or by mocking them using jest.mock .

I personally recommend avoiding imports completely on your modules by using dependency injection instead. If you really want to go the jest.mock way, all you have to do is use it with a factory , like this (from the Jest docs):

jest.mock('../moduleName', () => {
  return jest.fn(() => 42);
});

// This runs the function specified as second argument to `jest.mock`.
const moduleName = require('../moduleName');
moduleName(); // Will return '42';

Yes, this will magically replace the imported file before it is imported, avoiding the barrel file issue altogether.

Again: I don't like magic code and recommend you use dependency injection instead.

Barrel imports should be supported, at least if you are using ts-jest transform. Ref: https://github.com/kulshekhar/ts-jest/issues/1149

but I have encouraged similar problem with barrel type definition src/types/index.d.ts referenced usually as @/types all around in codebase

I ended up with configuring extra moduleNameMapper definition

 moduleNameMapper: {
    // FIXES Could not locate module @/types mapped as: .../src/types.
    '^@/types$': '<rootDir>/src/types/index.d',
  },

Hope this will help someone:)

Jest

I've had the same issue: Jest was throwing errors related to files that I didn't use , only because they were exported in a barrel file which was used by the modules I was testing. In my case the issue was with tests on Vuex stores which started to break because of modules using the same stores (probably due to circular dependencies).

prices.test.js

// the test imports a module
import prices from '@/store/modules/prices';

prices.js

// the module imports a module from a barrel file
import { isObject } from '@/common';

index.js

// this is the imported module
export { default as isObject } from './isObject';
// this is the other module that breaks my tests, even if I'm not importing it
export { default as getPageTitle } from './getPageTitle';

getPageTitle.js

// when the module uses the store, an error is thrown
import store from '@/store/store';

I think that in my case the issue was a circular dependency, anyway to answer your question: yes, Jest imports all the modules in the barrel files even if they are not imported directly.

In my case the solution was to move the modules that were using Vuex to a separate folder and to create a separate barrel file only for those files.

Webpack

In a different moment I figured out that Webpack is doing the same thing by default. I didn't notice it on a project where modules were small and weren't importing libraries, but on a separate project I had modules importing very large libraries and I noticed that Webpack wasn't doing tree-shaking and wasn't optimizing chunks as it was supposed to do.

I discovered that Webpack imports all the libraries in the barrel file, in a similar way as Jest does. The upside is that you can stop this behavior by disabling side effects on the specific barrel files.

webpack.config.js

{
  module: {
    rules: [
      // other rules...
      {
        test: [/src\/common\/index.ts/i],
        sideEffects: false,
      }
    ]
  }
}

You can see the difference using Webpack Bundle Analyzer : when side effects are not turned off, all imports from one specific folder will have the same size. On the other hand, when side effects are turned off you will see a different size for different imports.

Default behavior (all imports the same size)

默认行为(所有导入相同大小)

Side effects turned off (the size depends on which modules you import)

关闭副作用(大小取决于您导入的模块)

When files in the barrel files import large libraries, the improvements can be even more noticeable (tree-shaking works, chunks are optimized).

You can find more details here: https://github.com/vercel/next.js/issues/12557 and here: Webpack doesn't split a huge vendor file when modules are exported and imported using index files .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM