创建包含TypeScript中所有导出的模块 [英] Create Module containing all exports in TypeScript

查看:90
本文介绍了创建包含TypeScript中所有导出的模块的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我要实现的目标

我正在尝试在TypeScript中创建一个模块,其中将包含所需类的每次导出.实际上,令我困扰的实际上是我的类中都包含import语句,这些语句都是相对的,这并不是进行进一步维护的最佳方法.我只想使用绝对路径.

一旦被编译,具有绝对路径的类(在生成的JavaScript中)将具有错误的路径(因为它们不会转换为相对路径,与tsconfig.json> outDir选项结合使用时甚至更糟).

用法示例

让我们以下面的项目层次为例

root
|- helpers
   |- MyHelper.ts
   |- AnotherHelper.ts
|- calculators
   |- MyCalc.ts
|- parsers
   |- XmlParser.ts
   |- JsonParser.ts
|- index.ts // currently holding export statements

我想做什么

我想要一个这样的index.ts文件:

// 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
}

那么我可以执行以下操作:

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 ...
    }
}

我在很多地方都看到过(在SO上, TypeScript的github回购,. ..)我不是唯一一个在相对/绝对路径上苦苦挣扎的人.

我的实际问题是:是否可以创建这样的模块(或机械手),以便我可以在顶级容器中定义每个导出,并像使用NodeJS模块一样导入此容器(例如)?

我尝试过的事情

第一次尝试

这是我实际上拥有的index.ts文件:

// 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

第二次尝试

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
}

第三,第四,第N次尝试

尝试declare namespaceexport module等..没有运气.

也尝试使用declare module "IndexModule,也会导致错误:环境模块声明中的导入或导出声明无法通过相对模块名称引用模块

旁注

我不是NodeJS/模块/命名空间的专家,所以我可能不太了解.如果是这样,我希望能有人指出我误解了.

此处的相关链接显示了实际问题.

配置:

  • 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"
  }
}

解决方案

我终于设法得到了想要的东西.这里是步骤和有关它的解释.

1.架构示例

为说明问题/解决方案,让我们以树状结构为例

root
  \- A
     \- a1.ts
     \- a2.ts
  \- B
     \- D
        \- bd1.ts
        \- bd2.ts
     \- b1.ts
  \- C
     \- c1.ts
     \- c2.ts

有一个根文件夹,其中包含其他三个文件夹(A,B和C):

  • A包含两个文件(a1.tsa2.ts)
  • B包含一个包含两个文件的文件夹(D,分别包含bd1.tsbd2.ts)和一个文件(b1.ts)
  • C包含两个文件(c1.tsc2.ts)

因此,现在,如果bd1.ts需要在a1.ts中声明的类,那么我们目前将..

import { ClassFromA1 } from "../../A/a1"

..使用相对路径并且很难维护(IMO).在接下来的步骤中,我们将消除它.

2.创建具有所有导入内容的index.ts

index.ts文件的目的(我这样命名,您可以选择所需的名称)是将所有需要的类保存在一个文件中.

所以我们要做的是在树状目录的顶部node_modules中创建此文件,并导出项目中需要的每个类.这样,我们只需要index.ts文件即可检索所需的类.

这里是内容:

// 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"

现在我们已经知道了,以前的导入可以通过这种方式完成..

import { ClassFromA1 } from "../../index"

..但这仍然意味着我们使用相对路径,而不是最佳路径.

在项目中将rootDir设置为root文件夹可以解决此问题..

import { ClassFromA1 } from "index"

..并且有效!但是问题出在编译类上,在编译后的类中,路径不会被解析为相对的.

这意味着您的a1.js(已编译的a1.ts文件)仍将按原样设置导入设置,但由于不了解rootDir,这可能是错误的.

// a1.js
const index_1 = require("index") // supposed to be in the same package than the current file

3.选择node resolution策略

幸运的是,NodeJS具有node resolution策略,可以将其设置为解决节点模块导入的问题.这是节点模块解析策略的真正简短摘要可以在我们的项目中设置:

  • 检查导入路径
  • 如果导入路径以.../ =>开头,则是相对的
  • 如果导入路径以名称=>开头,则它是节点模块(这很重要)

NodeJS中模块的解析如下(始终从bs1.ts开始):

  • 检查root/B/D/node_modules/index.ts是否存在>否
  • 检查root/B/node_modules/index.ts是否存在>否
  • 检查root/node_modules/index.ts是否存在>是!

我们在这里所做的是,我们欺骗Typescript认为index.ts是NodeJS模块,并且必须将其解析为一个模块.

它将使用上述(和链接中)描述的模式来检索index.ts,然后我们可以从中导入所需的类,而无需相对/绝对路径.

4.按照我们的意愿进行导入

现在可以通过以下方式导入..

// no matter in which file we are
import { ClassFromA1 } from "index";
import { ClassFromBD1 } from "index";
// and so on ..

..并且运行良好,在VisualStudio上没有错误,也没有编译的类.欢迎对此解决方案发表任何意见.

干杯!

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 :

  • 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"
  }
}

解决方案

I finally managed to get something like I wanted. Here are the steps and an explanation about it.

1. Architecture example

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) :

  • A contains two files (a1.ts and a2.ts)
  • B contains a folder with two files (D, with bd1.ts and bd2.ts) and a file (b1.ts)
  • C contains two files (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.

2. Create an index.ts with all imports

The 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

3. Select the node resolution strategy

Fortunately, 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 :

  • Check the import path
  • If the import path begins with ., .., or / => it is relative
  • If the import path begins with a name => it is a node module (this is important)

The resolution of modules in NodeJS is as it follows (starting always from bs1.ts) :

  • Check if root/B/D/node_modules/index.ts exists > No
  • Check if root/B/node_modules/index.ts exists > No
  • Check if root/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.

4. Do imports as we wanted to

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 !

这篇关于创建包含TypeScript中所有导出的模块的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆