TypeScript 中的模块化架构





5.00/5 (5投票s)
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 帐户克隆本文的所有代码。