and how to defeat it
Valeriy Kuzmin co-authored by Tamas Hegedus
Moonfare, Berlin, 2022
The problem
// ClassA.ts
import { B } from "./ClassB";
export class A extends B {
}
export const UsefulStuff = "Useful";
// ClassB.ts
import { UsefulStuff } from './ClassA';
console.log(UsefulStuff);
export class B {
}
Modules?
ReferenceError: Cannot access 'UsefulStuff' before initialization
The cause
In order to prevent an infinite loop, an unfinished copy of the a.js exports object is returned to the b.js module. b.js then finishes loading, and its exports object is provided to the a.js module.
Why bother?
At moonfare
Cycle around src/common/entity/users/Investor.ts
src/common/entity/users/Investor.ts
new src/common/services/QuestionnaireService/SuitabilityStatuses.ts
src/common/services/QuestionnaireService/QuestionnaireServiceInterface.ts
src/common/entity/users/Investor.ts
1. Fully nominal dependency
// ClassA.ts
import { B } from "./ClassB";
export class A extends B {
}
export const UsefulStuff = "Useful";
// ClassB.ts
import { UsefulStuff } from './ClassA';
console.log(UsefulStuff);
export class B {
}
Demo: example-1
1. Solution - lift
How
Pros & Cons
2. Coupled implementation dependency
Demo: example-2
2. Solution - full decouple
Pros & Cons
2. Solution - apply service locator
Pros & Cons
2. Solution - use event calling
Pros & Cons
3. God class (e.g. 'router' switch)
Demo: example-3-4
3. God class (e.g. 'router' switch)
Demo: example-3-4
Note: it's a subproblem of 2, and sometimes not a root cause
3. Solution: apply events or even commands
Explicit routing is an improper implementation of events, do it right!
Pros:
Cons:
4. Bad barrel file
// f/a.ts
export const a = "a";
// f/b.ts
export const b = "b";
// f/index.ts
import {a} from "./a";
import {b} from "./b";
export {a, b};
// or
export * from "./a";
export * from "./b";
“… Because the problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle. “ — Joe Armstrong, creator of Erlang progamming language
A fitting quote
Why people use barrels?
Why barrels are bad?
Solution: Instead of barrels, use...
Come on, it's 2022, and you're not writing in a notepad
Solution: Instead of barrels, use auto-imports
Pros:
Cons:
5. ORM entity dependency
Demo: example-5
5. Solution - cut the link in one direction
Pros & Cons
5. Alternative solution - allow for models only
Pros & Cons
6. Hacky workarounds - delayed require
import x from "bad";
x.doStuff();
// convert into
setTimeout(() => {
const x = require("bad");
x.doStuff();
}, 0)
7. Hacky workarounds - type ignore
tsPreCompileDeps = false | "specify"
7. Hacky workarounds - type ignore
Demo: example-7
Questions?