模块 vs 命名空间:组织大型 TypeScript 项目的正确方式是什么?

41

我对TypeScript还比较陌生,正在为WebGL编写一个小型的原型框架。我目前正在重构我的项目,并遇到了一些问题,如何组织我的项目,因为模块和命名空间两种方法似乎都有严重的缺点。

本文不是关于如何使用这些模式,而是如何克服它们各自带来的问题。

当前情况:使用命名空间

从C#转过来,这似乎是最自然的方式。每个类/模块都会得到适当的命名空间,我在tsconfig.json中提供"outFile"参数,以便将所有内容连接成一个大文件。 编译后,我将根命名空间作为全局对象。依赖项没有构建到项目中,因此您必须在html中手动提供所需的*.js文件(不好)

示例文件

namespace Cross.Eye {
    export class SpriteFont {   
        //code omitted
    }    
}

使用示例(在提供JS文件的HTML中确保将Cross命名空间加载到全局命名空间中)

namespace Examples {
    export class _01_BasicQuad {
        context: Cross.Eye.Context;
        shader: Cross.Eye.ShaderProgram;

        //code omitted
    }
}

优点

  • 如果你是从C#/Java过来的,使用起来非常简单。
  • 与文件名无关 - 重命名文件不会破坏你的代码。
  • 易于重构:IDE可以轻松重命名命名空间/类,并且更改将一致地应用于整个代码。
  • 方便:将类添加到项目中只需要添加文件并在所需的命名空间中声明即可。

缺点

对于大多数项目,我们建议使用外部模块,并为快速演示和移植旧的JavaScript代码使用命名空间。

摘自:https://basarat.gitbooks.io/typescript/content/docs/project/namespaces.html

  • 根命名空间总是(?)全局对象(不好)
  • 不能(?)与browserify或webpack等工具一起使用,这对于将库与其依赖项捆绑在一起或在实际使用库时捆绑您的自定义代码与库非常重要。
  • 如果计划发布npm模块,则是不良做法

最新技术(?):模块

Typescript支持ES6模块,它们是新的和闪亮的,每个人似乎都同意它们是前进的方式。这个想法似乎是每个文件都是一个模块,并通过提供导入语句的文件可以非常明确地定义您的依赖项,这使得捆绑工具能够高效地打包您的代码。我大多数情况下每个文件都有一个类,这似乎与模块模式不太合适。

在重构后,这是我的文件结构:

enter image description here

此外,我在每个文件夹中都有一个index.ts文件,以便可以通过import * as FolderModule from "./folder"导入其所有类

export * from "./AggregateLoader";
export * from "./ImageLoader";
export * from "./TiledLoader";
export * from "./XhrLoaders";
export * from "./XmlSpriteFontLoader";

示例文件 - 我认为问题在这里变得清晰可见...

import {SpriteFont} from "./SpriteFont";
import {ISpriteTextGlyph, ISpriteChar} from "./Interfaces";
import {Event,EventArgs} from "../../Core";
import {Attribute, AttributeConfiguration} from "../Attributes";
import {DataType} from "../GlEnums";
import {VertexStore} from "../VertexStore";
import {IRectangle} from "../Geometry";
import {vec3} from "gl-matrix";

export class SpriteText {
    // code omitted
}

使用示例。正如您所见,我不再需要遍历命名空间,因为我可以直接导入类。

import {
    Context,
    Shader,
    ShaderProgram,
    Attribute,
    AttributeConfiguration,
    VertexStore,
    ShaderType,
    VertexBuffer,
    PrimitiveType
} from "../cross/src/Eye";

import {
    Assets,
    TextLoader
} from "../cross/src/Load";

export class _01_BasicQuad {
    context: Context;
    shader: ShaderProgram;

    // code omitted.
}

优点

  • 将代码与命名空间解绑,使代码更加模块化。
  • 可以使用打包工具(如browserfy或webpack),它们还可以打包所有的依赖项。
  • 在导入类时更加灵活,不再需要遍历命名空间链。

缺点

  • 如果每个类都是一个不同的文件,则非常繁琐,您将不得不一遍又一遍地输入相同的导入语句。
  • 重命名文件将破坏您的代码(不好)。
  • 重构类名不会传播到您的导入中(非常糟糕 - 可能取决于您的IDE,我使用的是vs-code)。

我认为这两种方法似乎都有缺陷。命名空间似乎非常过时,在大型项目中不实用且与常用工具不兼容,而使用模块则相当不方便,并且破坏了我最初适用TypeScript的一些功能。

在完美的世界里,我会使用命名空间模式编写我的框架,并将其导出为模块,然后可以导入并与其依赖项一起打包。但是这似乎没有可能,除非使用一些丑陋的技巧。

那么我的问题是:您如何处理这些问题?如何最小化每种方法所带来的缺点?

更新

在获得更多TypeScript和JavaScript开发经验之后,我不得不指出,模块可能是90%用例的最佳选择。

最后10%是希望使用TypeScript为其加入少许调味的全局命名空间遗留项目(顺便说一句,这很好用)。

通过更好的IDE支持,可以解决模块的许多问题。Visual Studio Code已经添加了自动模块解析功能,效果非常好。

1个回答

17

tl;dr:不要选择过去,选择未来:模块。

在ES6模块规范的早期草案中,有一个内联模块概念,然后在2013年9月被取消。然而,这个概念已经在TypeScript团队中实现了,在语言的第一个beta版本中,即2012年:它是内部模块。然后,在2014年7月发布了ES6模块的最终规范,没有内联模块。一年后,在2015年7月,随着TypeScript 1.5版本,内部模块被重命名命名空间,以避免与标准混淆。

命名空间是一个旧特性,不再是ECMAScript语言的一部分。 TypeScript团队将继续遵循标准。自2014年7月发布ECMAScript模块标准以来,TS命名空间方面没有任何改进。
ES6模块的缺点包括:如果每个类都是不同的文件,则非常繁琐,您将不得不一遍又一遍地输入相同的导入语句;重命名文件会破坏您的代码(不好);重构类名不会传播到您的导入中(非常不好-可能取决于您的IDE,我使用的是vs-code)。我们可以期望未来的IDE在这些问题上有所改善。第一个问题已经被WebStorm解决了。

这是一个古老的帖子,但有一些值得做的事情是将经常使用的模块复制到您的 node_modules 文件夹中。然后,您就可以无需路径导入它们了。 - EJ Mason
10
@EJMason 什么……一个典型的项目在.gitignore中有node_modules。虽然有很多原因,但我看不出这是个好主意。 - eirikrl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接