Create a module containing all TypeScript exports
What i am trying to achieve
I am trying to create a module in TypeScript that will contain every export of the required classes. In fact, I actually need to have instructions import
in my classes that are relative, which is not really the best way to further maintain it. I would like to use absolute paths only.
After translation, classes (in prepared JavaScript) with absolute paths have incorrect paths (since they are not translated to relative paths and even worse when used in conjunction with the tsconfig.json
> option outDir
).
Example usage
Start with the following project hierarchy as an 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 a file index.ts
like this:
// 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, 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 TypeScript github repo , ...) that I'm 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 that I can define each export in the top container and import that container as if I were using a NodeJS module (for example import * as moment from "moment"
)?
What i tried
Try first
This is the file index.ts
I 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 attempt
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, Nth attempts
Tried it declare namespace
, export module
etc. etc. no luck.
Tried also declare module "IndexModule
, results in error: import or export declaration in environment module declaration cannot refer to module via module relative name
Side note
I am not a NodeJS / Modules / Namespaces expert, so I may not have understood anything good. If so, I would appreciate it if someone could point out what I misunderstood.
Relevant link here that shows the actual problem.
Configuration:
- NodeJS v7.2
- TypeScript v2.2
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"
}
}
source to share
Finally I managed to get what I wanted. Here are the steps and explanations.
1. An example of architecture
To illustrate the problem / solution, take this tree structure 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):
- A file contains two (
a1.ts
anda2.ts
) - B contains a folder with two files (D, c
bd1.ts
andbd2.ts
) and a file (b1.ts
) - C contains two files (
c1.ts
andc2.ts
)
So, if bd1.ts
classes declared in are needed a1.ts
, we will currently do.
import { ClassFromA1 } from "../../A/a1"
.. which uses relative paths and is difficult to maintain (IMO). We'll get rid of this in the next steps.
2. Build index.ts
with all imports
The purpose of the file index.ts
(I named it like this, you can choose any name you want) is to keep all the needed classes in one file.
So what we're going to do is create this file at the top of our arborescence in the directory node_modules
and export every single class we need in our project. Thus, we only need a file index.ts
to extract the required class.
Here is the content:
// 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've got this, the previous import can be done this way.
import { ClassFromA1 } from "../../index"
.. but that still means using relative paths, not optimal paths.
Setting in project a rootDir
to folder root
can fix this problem.
import { ClassFromA1 } from "index"
.. and it works! But the problem arises with overflowed classes where paths are not resolved as relative after compilation.
This means that your a1.js
(compiled a1.ts
file) will still have the import set as it is, but will probably be wrong because it doesn't know rootDir
.
// a1.js
const index_1 = require("index") // supposed to be in the same package than the current file
3. Choose a strategy node resolution
Fortunately, NodeJS has a strategy node resolution
that can be set to allow import of the node module. Here is a really quick overview of the node module resolution strategy that can be installed in our project:
- Check the import path
- If the import path starts with
.
,..
or/
=>, this is relative - If the import path starts with name => it is a node module ( this is important )
The resolution of modules in NodeJS is as follows (starting always with bs1.ts
):
- Check if it exists
root/B/D/node_modules/index.ts
> No - Check if it exists
root/B/node_modules/index.ts
> No - Check if it exists
root/node_modules/index.ts
> Yes!
What we have done here is that we tricked Typescript into thinking that it index.ts
is a NodeJS module and that it should be resolved as one.
It will use the template described above (and in the link) to extract index.ts
, and from that we can then import the needed classes without relative / absolute paths.
4. Import as needed
Now the import can be done as follows.
// no matter in which file we are
import { ClassFromA1 } from "index";
import { ClassFromBD1 } from "index";
// and so on ..
.. and it works great, no errors in VisualStudio and no class overloads. Any comments on this solution are appreciated.
Greetings!
source to share