I noticed today I've been using TypeScript for years and really haven't blogged about it at all. I guess that's not surprising; it essentially does exactly what it says it does: Turns the Wild West of fully dynamic JavaScript development into a strongly-typed, well-controlled arena.

I love doing dynamic stuff with JavaScript (a dynamic language provides for a very particular set of mental challenges), but for the most part, aside from massaging payloads to and from servers (so when we jump concerns from, eg, client to API), it's A Very Bad Idea to go full dynamic. And that means I love TypeScript. It takes some of the silly putty of JavaScript and puts it into the hard Lego blocks of C#, if I can reuse an interview answer I gave once upon a time...


The use case

But as I prepare a large and reasonably mature .NET MVC/AngularJS codebase [sic] for TypeScript, I ran back into the age old issue of how to expose my exports. Is there a way to import an entire directory of files easily? No, not really, no there's not.

The usual shorthand for doing this is to import from barrel files. And that's probably the way to go to start, creating multiple child barrels per folder and exposing them in an uber-barrel at the top. We have scores of AngularJS files to move to TypeScript, and currently have a <script> import per file (no, literally a line of <script> per AngularJS source file) in the main cshtml page, all generated by a giant .NET MVC's bundle config. Ewww. So we cut each folder into its own import and import the parent barrel in the index.html equivalent.

(I'd mistakenly thought the bundles were log files, I think as in logrolling, another term I tend to remember in concept but forget the term, though with the growing importance of elections, I'm doing better remembering now.)


What's a barrel?

Here's a decent description of the advantage of barrels from a self-titled TypeScript Book on github.com:

Without a barrel, a consumer would need three import statements:

import { Foo } from '../demo/foo';
import { Bar } from '../demo/bar';
import { Baz } from '../demo/baz';

You can instead add a barrelย demo/index.tsย containing the following:

// demo/index.ts
export * from './foo'; // re-export all of its exports
export * from './bar'; // re-export all of its exports
export * from './baz'; // re-export all of its exports

Now the consumer can import what it needs from the barrel:

import { Foo, Bar, Baz } from '../demo'; // demo/index.ts is implied

Here's another source that give or take explains my use case:

Since we are using many components in app.routing.module.ts file, we must import their reference for .component.ts and .module.ts files. A general import statements would looks like this:

import { DashboardComponent } from './dashboard/dashboard.component';
import { LoginComponent } from './login/login.component';
import { SignUpComponent } from './sign-up/sign-up.component';
import { AccountOverviewComponent } from './dashboard/account/account-overview/account-overview.component';
import { AccountSecurityComponent } from './dashboard/account/account-security/account-security.component';
import { CartOverviewComponent } from './dashboard/cart/cart-overview/cart-overview.component';
import { CartItemDetailsComponent } from './dashboard/cart/cart-item-details/cart-item-details.component';
import { CartComponent } from './dashboard/cart/cart/cart.component';
import { ProductComponent } from './dashboard/products/product/product.component';
import { ProductsDetailsComponent } from './dashboard/products/products-details/products-details.component';
import { ProductsOverviewComponent } from './dashboard/products/products-overview/products-overview.component';
import { DashboardModule } from './dashboard/dashboard.module';
import { AccountModule } from './dashboard/account/account.module';
import { CartModule } from './dashboard/cart/cart.module';
import { LoginModule } from './login/login.module';
import { SignUpModule } from './sign-up/sign-up.module';
import { ProductsModule } from './dashboard/products/products.module';

...

Typescript module resolution picks up index.ts file from folder name if it is there and try to import packages.

In this file, we can export all folder specific components and modules. For example, consider index.ts file inside products folder:

export * from './product/product.component';
export * from './products-details/products-details.component';
export * from './products-overview/products-overview.component';
export * from './products.module';

...

Here are the complete import statement[s] using index.ts file approach

import { AccountOverviewComponent, AccountSecurityComponent, AccountModule } from './dashboard/account';
import { CartComponent, CartOverviewComponent, CartItemDetailsComponent, CartModule } from './dashboard/cart';
import { ProductComponent, ProductsDetailsComponent, ProductsOverviewComponent, ProductsModule } from './dashboard/products';
import { DashboardComponent, DashboardModule } from './dashboard';
import { LoginComponent, LoginModule } from './login'
import { SignUpModule, SignUpComponent } from './sign-up';

Looks neat and simple, [doesn't] it?


The evil side of barrels: Circular dependencies

As I got ready to put our AngularJS files into barrels for the TypeScript port, I recalled that thar be dragons in the barrel file. But I couldn't remember what it was, precisely.

Turns out it's that importing and exporting indiscriminately can run you into circular dependencies.

Here's a decent explanation:

Barrel File Caveats

Although its is not directly related to barrel files i-e circular dependency. Circular dependency occurs when a Module A somehow imports itself.

How circular dependency happens best explaned by technbuzz.com
B imports A, C imports B
Things gets works when one Module somehow import itself technbuzz.com
At the end of the day Module A imports itself

Make sure you donโ€™t get into this kind of trouble especially when using Barrel files.

And because everyone and their brother says circular dependencies, but don't actually explain where it'd happen, here's one real-world example of this issue:

From nestjs.com:

WARNING

A circular dependency might also be caused when using "barrel files"/index.ts files to group imports. Barrel files should be omitted when it comes to module/provider classes. For example, barrel files should not be used when importing files within the same directory as the barrel file, i.e.ย cats/cats.controllerย should not importย catsย to import theย cats/cats.serviceย file. For more details please also seeย this github issue.

The linked GitHub issue:

alexandr2110proย commentedย on Oct 9, 2018:

Minimal reproduction of the problem with instructions

Not sure. We've got nearly 50 modules and all are working fine.

And most of them actually useย AuthModuleย as you might guess.

Only this one, that I've created recently can't load. Any ideas on what am I doing wrong? :)

The begrudging fix:

alexandr2110proย commentedย on Oct 11, 2018:

So in the end, nothing has changed... I didn't remove/change the code. I've just moved 2 functions from separate files to the test file body. That's it!

And of course, the imports in the test file changed:

/* <...> */
import {
  createAccessToken,
  createAccount,
  createUser,
  // findOneInvitation,  <-- was here when I had an exception
  // findOneAccount,     <-- was here when I had an exception
  E2ETestingContainer,
  E2ETestingModulesFactory,
  findOneRole,
  findOneUser,
  FixtureLoader,
  FixtureLoaderConfig,
  JEST_E2E_TIMEOUT,
  MongooseFixturesService,
} from '../../testing';
/* <...> */

@kamilmysliwiec

I think we can close the issue if you want.
But if somebody can tell me what the hell is going on here, I'd greatly appreciate that!

The explanation:

kamilmysliwiecย commentedย on Oct 16, 2018:ย ย 

I would suggest omitting barrel files when it comes to module's providers/module class OR using them very carefully (for example, even if you create a barrel, you shouldn't use it from within the same module itself [only from outside] and when circular dependency may potentially appear).


Wait, huh?

I'm not going to swear I get it. I think there might be some interaction with dependency injection.

See this blog post on class declarations not hoisting in JavaScript and how order matters. I think a similar issue is illustrated (as in literally illustrated with a flowchart) here, but doesn't mention barrels.

See also this reddit post [sic] that lays out a circular dependency setup for related objects reasonably well.

Another real world use case. And here's some jive on circular references in general, elementary stuff, but perhaps useful.

From github.com:

Desired functionality.

An option to ignore these kind of circular deps
A => barrel => A

I think that's it in a nutshell. Something in the indiscriminately broad barrel is importing/exporting the same thing that started the chain. I'm suspicious it's actually A => barrel => Lots of difficult to follow dependencies => A, but you get the idea. If you import a barrel that could contain something used in the same library, you can easily get a circular dependency. Instead, you should roll the barrel in one directly only, never using it within your own self-contained module, only exposing its contents to use in another app that imports it.

And that makes sense with the original example of having helper functions that blow up. If your object imports helper functions and models that also use the helper functions you could be entering a world of pain. Though I'm still not 100% convinced. More like 50% sure I smell something.

And here's the bottom line from an anonymous poster:


Wait a minute. Didn't the Angular.io style guide used to encourage barrels?

Yes, yes it did. But though it originally encouraged them, the Angular style guide now doesn't mention barrels. This change used to be described on the Angular changelog page, but isn't there any more.

As of today, anyhow, you can find an archive of the warning here, if you're curious:

"Style Guide" withย NgModulesย (2016-09-27)

StyleGuideย explains recommended conventions for Angular modules (NgModule). Barrels now are far less useful and have been removed from the style guide; they remain valuable but are not a matter of Angular style.

Looks like you can find the history of this style guide here now, and here's what the style guide used to say about using barrels:

Create and Import Barrels

Style 04-10

Consider creating a file that imports, aggregates, and re-exports items. We call this technique a barrel.

Consider naming this barrel file index.ts.

Why? A barrel aggregates many imports into a single import.

Why? A barrel reduces the number of imports a file may need.

Why? A barrel provides a consistent pattern to import everything exported in the barrel from a folder.

Why? This is consistent with a pattern from Node, which imports the index.js|ts file from a folder.

Why? A barrel shortens import statements.


And that's the belated history of barrel file use for importing JavaScript/TypeScript files.


Btw, is anyone else going crazy with the new font choices at StackOverflow? Why did you sell, Joel? I hope you're enjoying your hats of money. ;^D

Labels: , ,