模块与命名空间:组织大型打字稿项目的正确方法是啥?

Posted

技术标签:

【中文标题】模块与命名空间:组织大型打字稿项目的正确方法是啥?【英文标题】:Modules vs. Namespaces: What is the correct way to organize a large typescript project?模块与命名空间:组织大型打字稿项目的正确方法是什么? 【发布时间】:2017-02-11 03:25:41 【问题描述】:

我对 typescript 很陌生,我正在为 WebGl 编写一个小型原型框架。我目前正在重构我的项目,并且在如何组织我的项目方面遇到了一些问题,因为(模块和命名空间)方法似乎都有严重的缺陷。

这篇文章不是关于如何使用这些模式,而是如何克服这些模式带来的问题。

现状:使用命名空间

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

示例文件

namespace Cross.Eye 
    export class SpriteFont    
        //code omitted
        

使用示例(您必须先确保将 Cross 命名空间加载到全局命名空间中,方法是在 html 中提供 js 文件)

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 等工具一起使用,这对于将 lib 与其依赖项捆绑或在实际使用时将您的自定义代码与 lib 捆绑至关重要。 如果您计划发布 npm 模块,这是一种不好的做法

最先进的 (?):模块

Typescript 支持 ES6 模块,它们是新的和闪亮的,每个人似乎都同意它们是要走的路。这个想法似乎是每个文件都是一个模块,通过在 import 语句中提供文件,您可以非常明确地定义您的依赖项,这使得捆绑工具可以轻松有效地打包您的代码。我通常每个文件都有一个类,这似乎不适用于 dhte 模块模式。

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

此外,我在每个文件夹中都有一个 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)

IMO 两种方法似乎都有缺陷。命名空间似乎非常过时,对于大型项目不切实际,并且在使用模块时与常用工具不兼容,非常不方便,并且破坏了我最初调整 typescript 的一些功能。

在一个完美的世界中,我会使用命名空间模式编写我的框架并将其导出为一个模块,然后可以将其导入并与其依赖项捆绑在一起。但是,如果没有一些丑陋的 hack,这似乎是不可能的。

所以这是我的问题:您是如何处理这些问题的?我怎样才能最大限度地减少每种方法所隐含的缺点?

更新

在获得了更多关于 typescript 和 javascript 开发的一般经验后,我不得不指出,模块可能是 90% 的用例。

最后 10% 希望是使用全局命名空间的遗留项目,你想用一点打字稿来增加趣味(顺便说一句,效果很好)。

我对模块的许多批评可以(并且已经)通过更好的 IDE 支持来解决。 Visual Studio Code 已经添加了自动模块解析,效果很好。

【问题讨论】:

【参考方案1】:

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

在 ES6 模块规范的早期草案中,有一个 内联模块 的概念,然后是 has been eliminated in September 2013。然而,TypeScript 团队已经在 2012 年实现了这个概念,该语言的第一个 beta 版本:它是内部模块。然后,没有内联模块的 ES6 模块 has been released in July 2014 的最终规范。一年后,也就是 2015 年 7 月,TypeScript 1.5 版,内部模块 has been renamed 到 namespaces 以避免与标准混淆。

命名空间是一项遗留功能。它不会成为 ECMAScript 语言的一部分。 TypeScript 团队将继续遵循该标准。自 2014 年 7 月 ECMAScript 模块标准发布以来,TS 命名空间没有任何改进。

[ES6 模块的]缺点

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

我们希望未来的 IDE 能够对这些问题进行一些改进。第一个已经被 WebStorm 解决了。

【讨论】:

这是一篇古老的帖子,但可能值得做的是将经常使用的模块复制到 node_modules 的文件夹中。然后你可以在没有路径的情况下导入。 @EJMason 什么......一个典型的项目在 gitignore 中有 node_modules。在众多原因中,我看不出这是一个好主意。

以上是关于模块与命名空间:组织大型打字稿项目的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

将节点模块导入角度打字稿/角度cli的正确方法是啥

在打字稿库项目中组织 NPM 模块导出的最佳实践?

使用打字稿在 Firebase Cloud 函数中动态导入类的正确方法是啥?

打字稿:引用 Vuex 存储模块会导致 VueJs 2.5 的命名空间错误

打字稿错误:“”仅指一种类型,但在此处用作命名空间

使用反应创建上下文时找不到命名空间“ctx”错误 - 打字稿