What am I trying to achieve
I am trying to create a module in TypeScript which would contain every export of needed classes. In fact, what bothers me actually is to have import
statements in my classes which are all relative, which is not really the best way for further maintenances. I would like to use absolute paths only.
Once transpiled, classes (in the produced JavaScript) with absolute paths have wrong paths (since they are not translated to relative paths, and even worse when used in conjunction with tsconfig.json
> outDir
option).
Example usage
Let's start with the following hierarchy of project as example
root
|- helpers
|- MyHelper.ts
|- AnotherHelper.ts
|- calculators
|- MyCalc.ts
|- parsers
|- XmlParser.ts
|- JsonParser.ts
|- index.ts // currently holding export statements
What I want to do
I would like to have an index.ts
file of this kind :
// a top-level module, resolved like the `node` strategy of NodeJS module resolution
export module IndexModule {
export { MyHelper } from "./helpers/MyHelper";
export { AnotherHelper } from "./helpers/AnotherHelper";
// do the same for every class in my project
}
So then I could do the following :
import { JsonParser } from "IndexModule"; // no relative nor absolute path
export class MyHelper {
public constructor(input: any){
var result: any = new JsonParser().parse(input);
// work with result ...
}
}
I've seen in many places (here on SO, the github repo of TypeScript, ...) that I am not the only one struggling with relative/absolute paths for imports.
My actual question is : Is it possible to create such a module (or mechanic) so I can define every export in a top container, and import this container as if I was using a NodeJS module (like import * as moment from "moment"
) ?
What I tried
First try
This is the index.ts
file I actually have :
// index.ts is in the root directory of the project, it accesses classes with relative paths (but here, management is quite easy)
export { Constants } from "./helpers/Constants";
export { ConstraintCalculatorHelper } from "./helpers/ConstraintCalculatorHelper";
export { MomentHelper } from "./helpers/MomentHelper";
export { Formatter, OutputFormatterHelper } from "./helpers/OutputFormatterHelper";
// ... doing this for each class I have in my project
Second try
declare module IndexModule {
export { Constants } from "./helpers/Constants";
export { ConstraintCalculatorHelper } from "./helpers/ConstraintCalculatorHelper";
export { MomentHelper } from "./helpers/MomentHelper";
export { Formatter, OutputFormatterHelper } from "./helpers/OutputFormatterHelper";
// error : Export declarations are not permitted in a namespace
}
Third, Fourth, N-th try
Tried to declare namespace
, export module
, etc etc .. without luck.
Tried to declare module "IndexModule
too, leads to an error : Import or export declaration in an ambient module declaration cannot reference module through relative module name
Side note
I am not an expert in NodeJS / Modules / Namespaces, so I maybe have not understood something well. If so, I would appreciate if someone can point out what I misunderstood.
Relevant link here that shows the actual problem.
Configuration :
tsconfig.json :
{
"compileOnSave": true,
"compilerOptions": {
"removeComments": false,
"sourceMap": true,
"module": "commonjs",
"target": "es6",
"noImplicitAny": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"outDir": "./bin/LongiCalc"
}
}
I finally managed to get something like I wanted. Here are the steps and an explanation about it.
To illustrate the problem/solution, let's take this arborescence as an example
root
\- A
\- a1.ts
\- a2.ts
\- B
\- D
\- bd1.ts
\- bd2.ts
\- b1.ts
\- C
\- c1.ts
\- c2.ts
There is a root folder, containing 3 other folders (A, B and C) :
a1.ts
and a2.ts
)bd1.ts
and bd2.ts
) and a file (b1.ts
)c1.ts
and c2.ts
)So now, if bd1.ts
needs classes declared in a1.ts
, we will currently do ..
import { ClassFromA1 } from "../../A/a1"
.. which uses relative paths and is hard to maintain (IMO). We will get rid of that in the next steps.
index.ts
with all importsThe purpose of the index.ts
file (I named it like this, you can choose whatever name you want) is to keep all needed classes in one single file.
So what we will do, is to create this file on the top of our arborescence in a node_modules
directory, and export every single class we need in our project. This way, we will only need the index.ts
file to retrieve the class we want.
Here are the contents :
// current position : root/node_modules/index.ts
export { ClassFromA1 } from "./A/a1.ts"
export { ClassFromA2 } from "./A/a2.ts"
export { ClassFromBD1 } from "./B/D/bd1.ts"
export { ClassFromBD2 } from "./B/D/bd2.ts"
export { ClassFromB1 } from "./B/b1.ts"
export { ClassFromC1 } from "./C/c1.ts"
export { ClassFromC2 } from "./C/c2.ts"
Now that we have got this, the previous import can be done this way ..
import { ClassFromA1 } from "../../index"
.. but this still implies us to use relative paths, not optimal.
Setting in the project a rootDir
to the root
folder can solve this problem ..
import { ClassFromA1 } from "index"
.. and it works ! But the problem happens with transpiled classes, where paths are not resolved as relative once compiled.
This means your a1.js
(compiled a1.ts
file) will still have the import set as it is, but will probably be wrong because it has no knowledge of rootDir
.
// a1.js
const index_1 = require("index") // supposed to be in the same package than the current file
node resolution
strategyFortunately, NodeJS has a node resolution
strategy that can be set to resolve node module imports. Here is a really brief summary of the Node module resolution strategy that can be set in our project :
.
, ..
, or /
=> it is relativeThe resolution of modules in NodeJS is as it follows (starting always from bs1.ts
) :
root/B/D/node_modules/index.ts
exists > Noroot/B/node_modules/index.ts
exists > Noroot/node_modules/index.ts
exists > Yes !What we have done here, is that we tricked Typescript to think that index.ts
is a NodeJS module and that it must be resolved as one.
It will use the pattern described above (and in the link) to retrieve index.ts
, and from it we can then import the needed classes without relative / absolute paths.
Now imports can be done the following way ..
// no matter in which file we are
import { ClassFromA1 } from "index";
import { ClassFromBD1 } from "index";
// and so on ..
.. and it works nicely, no errors on VisualStudio, nor transpiled classes. Any remarks on this solution is welcome.
Cheers !
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With