65.9K
CodeProject 正在变化。 阅读更多。
Home

TypeScript 中的模块化架构

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2016 年 7 月 20 日

CPOL

6分钟阅读

viewsIcon

8978

TypeScript 中的模块化架构

引言

本文介绍了 TypeScript 开发者如何组合他们的模块并使用组合后的 JavaScript 输出。

在其他预期的项目中实现功能。假设您的应用程序有几个功能,您想打包应用程序的每个部分并使用它。

本文将从操作层面实现这些功能。

背景

许多开发人员在其项目中使用基于 AMD (Asynchronous Module Definition) 的模块,为此我们需要使用第三方工具,例如 requirejs,并在 TypeScript 代码中使用 "import/from" 保留字来导入其他模块。

例如,模块 "B" 需要模块 "A" 的功能,我们将模块 "A" 导入到模块 "B" 中,并要求异步加载模块 "A"。

每个模块都可以导入到其他模块中,requirejs 应该能够找到所有导入,使用正则表达式并异步加载其他文件。我们知道 requirejs 为了获得更好的性能,使用了单例模式,加载的模块不需要对已加载的文件进行重复处理。

在本文中,我们希望使用 TypeScript 内置的编译器来组合和打包模块,并创建现代架构。

解决方案

我使用我最喜欢的 IDE "Visual Studio",您几乎可以使用任何 IDE/文本编辑器(但您可能需要对本文进行一些小的改动才能成功构建)。

首先,我将使用 Visual Studio 创建一个空的 ASP.NET 项目,不添加任何额外的引用。

我将此项目命名为 "architect",这就是我的解决方案。

这是显而易见的,非常清晰。

如果您没有 TypeScript,您可以轻松地从 typescriptlang 安装它。我使用的是 Visual Studio 2015 Update 2,所以它已经安装在我的机器上了。

太棒了,我在项目根目录下创建了一个名为 "Application" 的文件夹,并在其中创建了两个子文件夹,例如 "Foundation" 和 "Services"。

所以,我们有一个 "Application" 文件夹,里面有 "Foundation" 和 "Services"。

现在,我们要创建一些 TypeScript 文件来处理我们的架构功能。

在 "Foundation" 文件夹内,我创建了两个名为 "foundation1.ts" 和 "foundation2.ts" 的 TypeScript 文件。我打开 foundation1.ts 文件,代码如下:

// Application/Foundation/foundation1.ts 

module Foundation {
    export class foundation1 {
        static method1(str: string) {
            console.log(`method1 in foundation1: ${str}`);
        }
    }
}

Foundation.foundation1.method1('Author => Ali Khalili');

我认为对于任何 TypeScript 开发者来说,它的代码都是显而易见的,无需再做过多解释。

现在,我想在 foundation2.ts 中编写如下代码:

// Application/Foundation/foundation2.ts

module Foundation {
    export class foundation2 {
        static method2(str: string) {
            console.log(`method1 in foundation2: ${str}`);
        }
    }
}

您会看到这个文件与 foundation1.ts 完全相同,只是命名不同。

现在,我们需要 "tsconfig.json" 来管理此根目录中的 TypeScript 编译器,并将这两个文件合并成一个文件。

在 Visual Studio 中,您可以选择 "添加 - 新项",找到 "TypeScript JSON 配置",然后在 Foundation 文件夹中创建它,这样您就有了 "tsconfig.json"。

如果您找不到 TypeScript JSON 配置,没问题,您可以手动创建一个 tsconfig.json 文件并填写如下内容:

// Application/Foundation/tsconfig.json

{
  "compilerOptions": {
    //"watch": true,
    "noImplicitAny": false,
    "noEmitOnError": true,
    "removeComments": false,
    "sourceMap": false,
    "target": "es6",
    "outFile": "outPutFoundation.js"
  },
  "compileOnSave": true,
  "exclude": [
    "node_modules",
    "wwwroot"
  ]
}

这意味着编译器必须生成 es6 代码,并且 `outFile` 将创建并命名为 "outPutFoundation.js"。

太棒了,保存并构建您的解决方案,现在您应该看到类似这样的内容:

如果您没有看到 outPutFoundation.js,只需在 "解决方案资源管理器" 中单击 "显示所有文件" 即可。

如果您看到像 "foundation1.js" 和 "foundation2.js" 这样的额外文件,这是因为 Visual Studio 启动了初始的 TypeScript 编译器。请删除它们,在创建 tsconfig.json 之后,这些文件将不会再次出现。

好的,我想让您查看 outPutFoundation.js 并仔细检查它。

// Application/Foundation/outPutFoundation.js

var Foundation;
(function (Foundation) {
    class foundation1 {
        static method1(str) {
            Foundation.foundation2.method2(str);
            console.log(`method1 in foundation1: ${str}`);
        }
    }
    Foundation.foundation1 = foundation1;
})(Foundation || (Foundation = {}));
Foundation.foundation1.method1('Author => Ali Khalili');
var Foundation;
(function (Foundation) {
    class foundation2 {
        static method2(str) {
            console.log(`method1 in foundation2: ${str}`);
        }
    }
    Foundation.foundation2 = foundation2;
})(Foundation || (Foundation = {}));

是不是很棒?通过我们在 Foundation 文件夹中创建的 tsconfig,它承诺将所有 TypeScript 文件编译并合并成一个文件。稍后我们可以使用 gulp.js 等第三方工具对其进行最小化处理。本文不包含这部分内容,但您可以在 gulpjs 上找到更多信息。

好的,现在我们需要在项目根目录下创建一个 index.html 文件,创建它并引用 outPutFoundation.js。运行项目并在浏览器控制台中,您将看到这个字符串:

"method1 in foundation1: Author => Ali Khalili"

正如您所见,TypeScript 编译器首先创建了 foundation1,然后是 foundation2。假设 foundation1 中的 method1 调用 foundation2 类中的 method2,

我们需要在 foundation1 代码中做一些修改,如下所示:

module Foundation {
    export class foundation1 {
        static method1(str: string) {
            foundation2.method2(str);
            console.log(`method1 in foundation1: ${str}`);
        }
    }
}

Foundation.foundation1.method1('Author => Ali Khalili');

会发生什么?正如您可能猜测的那样,运行项目后会出现错误,因为这行添加的代码!

foundation2.method2(str);

这行代码会出错,因为 foundation2 尚未创建,并且在 foundation1 之后被调用。现在,我们该如何解决这个问题?

有一个简单的解决方案,只需在 foundation1.ts 的顶部添加一个 "reference path",它会明确告诉 TypeScript 编译器,这个文件需要 foundation2.ts。

所以,我们应该将 foundation1.ts 修改如下:

/// <reference path="foundation2.ts" />

module Foundation {
    export class foundation1 {
        static method1(str: string) {
            foundation2.method2(str);
            console.log(`method1 in foundation1: ${str}`);
        }
    }
}

Foundation.foundation1.method1('Author => Ali Khalili');

只需保存项目,就发生了令人难以置信的事情。TypeScript 编译器会自动更改目标文件,现在 outPutFoundation.js 已更改为如下内容:

var Foundation;
(function (Foundation) {
    class foundation2 {
        static method2(str) {
            console.log(`method1 in foundation2: ${str}`);
        }
    }
    Foundation.foundation2 = foundation2;
})(Foundation || (Foundation = {}));
/// <reference path="foundation2.ts" />
var Foundation;
(function (Foundation) {
    class foundation1 {
        static method1(str) {
            Foundation.foundation2.method2(str);
            console.log(`method1 in foundation1: ${str}`);
        }
    }
    Foundation.foundation1 = foundation1;
})(Foundation || (Foundation = {}));
Foundation.foundation1.method1('Author => Ali Khalili');

正如您所见,我们的代码已更改,先调用 foundation2,然后调用 foundation1!现在一切都很好,没有任何问题,您将在浏览器控制台中看到字符串输出。

多项目架构

但是,如果您有多个项目,或者您想惰性加载某些项,将所有 TypeScript 文件打包成一个文件是不合逻辑的!

现在是时候用一些 TypeScript 文件来实现 Services 了。在这部分,我想向您展示 TypeScript 编译器中的一些神奇之处。

在 Services 文件夹内创建一个 "service1.ts"。请注意,此时因为缺少 tsconfig.json,保存或构建后,编译器不会生成其 js 文件!

现在,在此文件夹内,我们创建一个 tsconfig.json 文件,并将 `outFile` 改为 "outPutService.js"。

我们的解决方案现在是这样的:

太棒了,假设我们想在 foundation1 中使用 service1 及其功能。当然,我们不能使用 import,因为我们的模块架构完全是不同的方式构建的。

在 index.html 中,我们应该有一些类似这样的代码:

// index.html

<h1>Hello World</h1>

<script src="Application/Services/outPutService.js"></script>

<script src="Application/Foundation/outPutFoundation.js"></script>

好的,我们在 foundation 之前调用 service,那么如何调用 service1.method1('some string')?

TypeScript 有一个名为 "Ambient Modules" 的功能,可以让我们声明所有需要的文件。

所以,再次想象一下我们要做什么;我们想在 foundation1 中使用 service1 的功能。为此,我们调用了 outPutService.js 而不是 outPutFoundation.js。这没问题,但如何调用 foundation1 中需要的函数呢?

正如我所说的,答案是 ambient modules。

在 Foundation 文件夹内,创建一个 Definition 文件夹,并在其中创建一个名为 "service1.d.ts" 的 TypeScript 文件,其代码如下:

// Foundation/Definition/service1.d.ts

declare module Service {
    class service1 {
        static method1(str: string): void;
    }
}

正如您所见,我们声明了 "Service" 模块,它的类等等。

现在我们可以调用此函数并享受强大的类型支持。

我们应该将 foundation1.ts 代码修改如下:

/// <reference path="foundation2.ts" />

module Foundation {
    export class foundation1 {
        static method1(str: string) {
            Service.service1.method1(str);
            foundation2.method2(str);
            console.log(`method1 in foundation1: ${str}`);
        }
    }
}

Foundation.foundation1.method1('Author => Ali Khalili');

一切都完成了;现在运行项目并在浏览器中查看控制台。

您应该会看到一些令人惊叹的东西,如下所示:

结论

当然,本文仅具有教育意义。例如,在这种情况下,您可以打包所有文件,但如果您有一个大型且可扩展的应用程序,此解决方案可以在各种问题上帮助您,并使您的应用程序更清晰、更易于使用。

代码

您可以通过我的 GitHub 帐户克隆本文的所有代码。

Github

© . All rights reserved.